efaimo 0.1.0 → 0.1.1
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/CHANGELOG.md +97 -2
- package/README.md +88 -30
- package/SKILL.md +13 -1
- package/dist/check/check.js +6 -2
- package/dist/cli.js +78 -4
- package/dist/clients/rawprobe.d.ts +21 -5
- package/dist/clients/rawprobe.js +101 -43
- package/dist/clients/stateless.js +17 -14
- package/dist/core/engine.js +20 -2
- package/dist/core/grade.js +7 -1
- package/dist/core/types.d.ts +22 -3
- package/dist/reporters/markdown.js +16 -4
- package/dist/reporters/pretty.js +36 -25
- package/dist/rules/injection.js +7 -0
- package/dist/rules/mcp/index.d.ts +14 -0
- package/dist/rules/mcp/index.js +68 -24
- package/dist/skills/parse.js +39 -3
- package/dist/targets/clientConfigs.d.ts +7 -0
- package/dist/targets/clientConfigs.js +4 -1
- package/dist/testing/anthropicRunner.js +2 -0
- package/dist/testing/harness.d.ts +21 -0
- package/dist/testing/harness.js +62 -11
- package/dist/testing/openaiRunner.js +8 -1
- package/dist/testing/stats.d.ts +32 -0
- package/dist/testing/stats.js +91 -0
- package/dist/util/dotenv.d.ts +9 -1
- package/dist/util/dotenv.js +11 -1
- package/dist/util/safeText.d.ts +8 -0
- package/dist/util/safeText.js +48 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/weigh/weigh.js +3 -1
- package/dist/weigh/window.d.ts +26 -0
- package/dist/weigh/window.js +44 -0
- package/docs/METHODOLOGY.md +70 -0
- package/docs/RULES.md +29 -23
- package/examples/contoso-crm-import/SKILL.md +21 -0
- package/examples/csv-cleanup/SKILL.md +17 -0
- package/examples/scenario.crm.yaml +22 -0
- package/examples/scenario.example.yaml +17 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,102 @@ All notable changes to efaimo are documented here. Format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project uses
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
-
## [
|
|
7
|
+
## [0.1.1] - 2026-08-02
|
|
8
|
+
|
|
9
|
+
Everything below was on `main` and unpublished. That gap was itself the
|
|
10
|
+
problem: `efaimo@0.1.0` still tells every user "the spec finalizes
|
|
11
|
+
2026-07-28" (a future tense about a date that has passed), the README
|
|
12
|
+
documents `--strict-readiness` and `--window`, and neither flag exists in
|
|
13
|
+
0.1.0. Cutting this release is what makes the documentation true.
|
|
14
|
+
|
|
15
|
+
The MCP specification published on 2026-07-28. The readiness rules here had
|
|
16
|
+
been written against the Release Candidate locked 2026-05-21, and the two are
|
|
17
|
+
not the same document: `DiscoverResult.serverInfo` was deleted,
|
|
18
|
+
`DiscoverResult` became cacheable, and three error codes were renumbered. The
|
|
19
|
+
entries below that name the published spec are the repairs, plus the tooling
|
|
20
|
+
that notices the next revision by itself.
|
|
21
|
+
|
|
22
|
+
### Security
|
|
23
|
+
|
|
24
|
+
- `--client` now prints the actual command of every server it is about to
|
|
25
|
+
spawn, and warns by name when a config came from the CURRENT DIRECTORY
|
|
26
|
+
rather than your user config. It reads `.mcp.json`, `.cursor/mcp.json` and
|
|
27
|
+
`.vscode/mcp.json` from the working directory and executes what it finds, so
|
|
28
|
+
running the audit inside a repository you cloned was arbitrary code
|
|
29
|
+
execution, announced only by a friendly config key.
|
|
30
|
+
- Terminal control characters are stripped from anything the audited target
|
|
31
|
+
supplied, at the render boundary of the pretty and markdown reporters. A
|
|
32
|
+
hostile server could put ESC[1A ESC[2K in a tool name and rewrite the grade
|
|
33
|
+
line printed above its own finding, so the reader saw "grade A (100), 0
|
|
34
|
+
errors" while the contradiction was erased. `--no-color` did not help; that
|
|
35
|
+
only suppresses colours efaimo adds.
|
|
36
|
+
- `.env` loading is restricted to `ANTHROPIC_API_KEY` and `OPENAI_API_KEY`.
|
|
37
|
+
It previously set ANY key from a working-directory `.env`, so a cloned
|
|
38
|
+
repository could set `NODE_TLS_REJECT_UNAUTHORIZED=0` and silently disable
|
|
39
|
+
certificate verification for later HTTPS calls, including ones carrying your
|
|
40
|
+
key.
|
|
41
|
+
- Skill references that resolve outside the skill directory are no longer read
|
|
42
|
+
by `weigh`. S106 already warned about them, but only after the read.
|
|
43
|
+
- `SKILL.md` is read through the same 512KB-class cap as referenced files
|
|
44
|
+
(2MB), instead of an uncapped read.
|
|
45
|
+
- The published composite Action no longer interpolates its inputs into a bash
|
|
46
|
+
`run:` body, which was script injection in the consumer's runner.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- `check --strict-readiness`: exit non-zero when the 2026-07-28 migration diff
|
|
51
|
+
is not clean. Readiness still never touches the grade or the badge, and the
|
|
52
|
+
default exit code is unchanged. It exists because a team that had finished
|
|
53
|
+
migrating had no way to stay migrated: `--strict` covers quality only, so
|
|
54
|
+
nothing in CI could catch a regression back onto the legacy handshake.
|
|
55
|
+
- `--window <tokens>` on `weigh` and `check`: report the context-window share
|
|
56
|
+
against a window you name instead of the 1,000,000-token default. Absolute
|
|
57
|
+
token counts do not change; only the denominator does.
|
|
58
|
+
- `npm run spec:drift`, plus a daily `spec-drift` workflow: fails when an MCP
|
|
59
|
+
revision newer than the one efaimo targets publishes, or when the targeted
|
|
60
|
+
revision changes under its own tag. The 2026-07-28 publication went unnoticed
|
|
61
|
+
in this repo for four days because nothing watched the spec itself.
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- `check --conformance` pins `@modelcontextprotocol/conformance@0.2.0-alpha.10`
|
|
66
|
+
rather than the floating `alpha` tag, which had moved off `alpha.9` on
|
|
67
|
+
2026-07-27. A dist-tag is not a pin, and the old one meant reporting "the
|
|
68
|
+
official conformance suite" for a build nobody here had run.
|
|
69
|
+
- Readiness output no longer claims the spec "finalizes 2026-07-28". It has
|
|
70
|
+
published; the diff still prints ungraded.
|
|
71
|
+
- Rule detail lines name the revision by its date instead of calling it "the
|
|
72
|
+
RC", which it no longer is.
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
|
|
76
|
+
- `efaimo test` reports a two-sided Fisher exact p and a 95% interval, and
|
|
77
|
+
`helps`/`hurts` require p < 0.05. The old rule was a +-15 point threshold at
|
|
78
|
+
a default of 5 trials per arm, where 5/5 against 4/5 is +20 points and
|
|
79
|
+
p = 1.0000. Judge now runs at temperature 0; an unparseable judge verdict is
|
|
80
|
+
excluded rather than scored FAIL; default trials 5 -> 20.
|
|
81
|
+
- E123 fires. The matcher was `(delete|...)` and `_` is a word character,
|
|
82
|
+
so `delete_file`, `deleteFile` and `drop_table` never matched: the rule was
|
|
83
|
+
dead for every conventionally named tool.
|
|
84
|
+
- A rule that throws is reported as an ungraded E000 instead of being silently
|
|
85
|
+
swallowed, which used to RAISE the grade.
|
|
86
|
+
- `weigh`'s heaviest-tools column derives its width from the rows printed, so
|
|
87
|
+
a 30-character tool name no longer shifts the number column.
|
|
88
|
+
- The median in `scripts/skills-index.mjs` is the true median.
|
|
89
|
+
- `check --mcp` could hang forever on a stdio server. The readiness probes
|
|
90
|
+
cleaned up their spawned servers only on the happy path, so a probe that threw
|
|
91
|
+
left the child's stdin open; the child never saw EOF, never exited, and its
|
|
92
|
+
stdio handles kept the CLI's event loop alive. The report printed and then the
|
|
93
|
+
process simply never returned. Cleanup now runs in a `finally`, matching the
|
|
94
|
+
stateless introspection path next door, which already did.
|
|
95
|
+
- `check --mcp` and `weigh` lost server identity on servers that had finished
|
|
96
|
+
migrating. The published spec deleted `DiscoverResult.serverInfo` and moved
|
|
97
|
+
identity into `_meta["io.modelcontextprotocol/serverInfo"]`, and efaimo read
|
|
98
|
+
only the Release Candidate's field. Both shapes are read now, newest first.
|
|
99
|
+
- E118 measured cache fields on `tools/list` only, while its own detail line
|
|
100
|
+
already listed `server/discover`. The published spec made `DiscoverResult`
|
|
101
|
+
extend `CacheableResult`, so that surface is required now, is measured now,
|
|
102
|
+
and the message names whichever surface failed.
|
|
8
103
|
|
|
9
104
|
## [0.1.0]
|
|
10
105
|
|
|
@@ -43,5 +138,5 @@ First release.
|
|
|
43
138
|
Documented rule set (`docs/RULES.md`), token methodology (`docs/METHODOLOGY.md`),
|
|
44
139
|
and integration guide (`docs/INTEGRATIONS.md`).
|
|
45
140
|
|
|
46
|
-
[
|
|
141
|
+
[0.1.1]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.1.1
|
|
47
142
|
[0.1.0]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@ fires at all. Good tools exist for single slices of that problem; efaimo's job i
|
|
|
21
21
|
the whole audit in one command, for both halves of what an agent loads. Lint a
|
|
22
22
|
skill, weigh a server's tool definitions, get a migration diff for the 2026-07-28
|
|
23
23
|
stateless MCP spec, and measure whether a skill actually improves task completion
|
|
24
|
-
([SkillsBench](https://www.skillsbench.ai) found
|
|
25
|
-
|
|
24
|
+
([SkillsBench](https://www.skillsbench.ai) found curated skills made agents
|
|
25
|
+
worse on 16 of 84 tasks; linting alone cannot catch that).
|
|
26
26
|
|
|
27
27
|
## Agent Skills
|
|
28
28
|
|
|
@@ -35,11 +35,11 @@ Point it at one skill or a whole folder. It validates each skill against the
|
|
|
35
35
|
|
|
36
36
|
```text
|
|
37
37
|
check skill claude-api
|
|
38
|
-
grade C (
|
|
38
|
+
grade C (73) 1 error 2 warnings 4 info
|
|
39
39
|
|
|
40
40
|
x S101 description is 1068 chars (spec max 1024)
|
|
41
41
|
! S104 instructions are ~18.4k tokens (spec recommends staying under 5k)
|
|
42
|
-
fix: move detail into references/ files loaded on demand
|
|
42
|
+
fix: move detail into references/ files loaded on demand (progressive disclosure)
|
|
43
43
|
! S104 SKILL.md is 570 lines (spec recommends under 500)
|
|
44
44
|
i S104 metadata level is ~294 tokens; the spec targets ~100 and this loads at
|
|
45
45
|
startup for every installed skill
|
|
@@ -54,7 +54,7 @@ hygiene. Run it on a folder and every skill gets its own grade, not one aggregat
|
|
|
54
54
|
`anthropics/claude-cookbooks`, and `obra/superpowers`, at pinned commits. 97%
|
|
55
55
|
score an A, but even this curated set has real issues: Anthropic's own
|
|
56
56
|
`claude-api` scores a C with an over-limit description and ~18k-token
|
|
57
|
-
instructions, and the median skill's instructions run ~1,
|
|
57
|
+
instructions, and the median skill's instructions run ~1,632 tokens loaded on
|
|
58
58
|
every trigger. Full report, the corpus manifest, and the two commands that
|
|
59
59
|
reproduce it: [the Skills Quality Index](./research/skills-index/REPORT.md).
|
|
60
60
|
|
|
@@ -103,9 +103,19 @@ test contoso-crm-import helps on an unknowable format
|
|
|
103
103
|
</details>
|
|
104
104
|
|
|
105
105
|
The first is pure context overhead, the second earns its tokens, and only the
|
|
106
|
-
trial data tells them apart.
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
trial data tells them apart.
|
|
107
|
+
|
|
108
|
+
The verdict is a significance test, not a threshold on the gap. Every run
|
|
109
|
+
reports a two-sided Fisher exact `p` and a 95% interval on the delta, and
|
|
110
|
+
`helps` / `hurts` require `p < 0.05`; anything else is `no measurable effect`
|
|
111
|
+
no matter how large the gap looks. That matters more than it sounds: at the
|
|
112
|
+
old default of 5 trials per arm, 5/5 against 4/5 is +20 points and a `p` of
|
|
113
|
+
exactly 1.0000, and the previous rule called it `helps`. The `+100` run above
|
|
114
|
+
is `p = 0.0002`; a `+25` run at 8 trials per arm is `p = 0.47` and now says so.
|
|
115
|
+
The default is 20 trials per arm, because below about that a partial result
|
|
116
|
+
cannot reach significance at all.
|
|
117
|
+
|
|
118
|
+
It is opt-in because a live run spends tokens on your key. See
|
|
109
119
|
[examples/scenario.example.yaml](./examples/scenario.example.yaml) and
|
|
110
120
|
[examples/scenario.crm.yaml](./examples/scenario.crm.yaml).
|
|
111
121
|
|
|
@@ -128,18 +138,21 @@ stateless protocol, so a 2026-07-28 server audits fine) and reports two things,
|
|
|
128
138
|
separately. First, a **quality grade** for what models actually experience:
|
|
129
139
|
descriptions, schemas, annotations, tool count, token cost. Second, an ungraded
|
|
130
140
|
**migration diff** for the 2026-07-28 stateless spec (which removes
|
|
131
|
-
`initialize
|
|
141
|
+
`initialize` and sessions, deprecates Sampling, Roots, and Logging, and requires
|
|
132
142
|
`server/discover`, `resultType`, and cache fields): exactly what will break and
|
|
133
143
|
how to fix it, each rule naming the SEP it came from. Readiness never drags the
|
|
134
|
-
grade
|
|
135
|
-
|
|
144
|
+
grade, because the two answer different questions: the grade is what a model
|
|
145
|
+
experiences from this tool surface, readiness is whether a 2026-07-28 client can
|
|
146
|
+
talk to this server. Combining them scored the reference server a C for a tool
|
|
147
|
+
surface that is an A. Pass `--strict-readiness` when you want CI to fail on
|
|
148
|
+
the diff. Full list: [docs/RULES.md](./docs/RULES.md).
|
|
136
149
|
|
|
137
150
|
<p align="center">
|
|
138
151
|
<img src="assets/check.png" alt="efaimo check example output: quality grade A (95) plus a four-item 2026-07-28 migration diff for the reference server" width="800">
|
|
139
152
|
</p>
|
|
140
153
|
|
|
141
|
-
*(That is the official reference server: solid quality, four things to
|
|
142
|
-
|
|
154
|
+
*(That is the official reference server: solid quality, four things left to
|
|
155
|
+
migrate.)*
|
|
143
156
|
|
|
144
157
|
<details>
|
|
145
158
|
<summary>The same output as copyable text</summary>
|
|
@@ -151,8 +164,7 @@ grade A (95) quality: 0 errors 1 warning 0 info
|
|
|
151
164
|
! E122 tool "echo": description misses 3/4 quality axes (length 40..600;
|
|
152
165
|
says when to use it; mentions the result)
|
|
153
166
|
|
|
154
|
-
2026-07-28 readiness 4 items to migrate (a migration diff, not graded
|
|
155
|
-
spec finalizes 2026-07-28)
|
|
167
|
+
2026-07-28 readiness 4 items to migrate (a migration diff, not graded)
|
|
156
168
|
! E104 server declares the logging capability; MCP Logging is deprecated in
|
|
157
169
|
2026-07-28 (SEP-2577) and logging/setLevel is removed
|
|
158
170
|
! E106 server/discover is not implemented (-32601 Method not found)
|
|
@@ -192,19 +204,56 @@ your context window.
|
|
|
192
204
|
Focused tools already own single slices of this space, and some are very good.
|
|
193
205
|
efaimo is the one tool that covers the whole audit surface. As of mid-2026:
|
|
194
206
|
|
|
195
|
-
| | efaimo | [skill-validator](https://github.com/agent-ecosystem/skill-validator) | [upskill](https://github.com/huggingface/upskill) | [mcp-spec-check](https://github.com/Roee-Tsur/mcp-spec-check) | [conformance](https://github.com/modelcontextprotocol/conformance) (official) |
|
|
196
|
-
|
|
197
|
-
| Agent Skills linting | yes | yes | no | no | no |
|
|
198
|
-
| Skill token cost | yes (3-level split) | yes | no | no | no |
|
|
199
|
-
| Skill outcome testing | with/without A/B trials (experimental) | static LLM scoring | yes (generate + eval, code agents) | no | no |
|
|
200
|
-
| MCP tool-definition cost | yes (3 serializations, `--anthropic` exact) | no | no | no | no |
|
|
201
|
-
| MCP quality rules | yes | no | no | no | no |
|
|
202
|
-
| 2026-07-28 readiness | migration diff: what breaks and how to fix | no | no | yes/no verdict | official test suite |
|
|
203
|
-
| CI budget gate + badge | yes | no | no | no | no |
|
|
207
|
+
| | efaimo | [skill-validator](https://github.com/agent-ecosystem/skill-validator) | [skillgrade](https://github.com/mgechev/skillgrade) | [upskill](https://github.com/huggingface/upskill) | [mcp-spec-check](https://github.com/Roee-Tsur/mcp-spec-check) | [conformance](https://github.com/modelcontextprotocol/conformance) (official) |
|
|
208
|
+
|---|---|---|---|---|---|---|
|
|
209
|
+
| Agent Skills linting | yes | yes | no | no | no | no |
|
|
210
|
+
| Skill token cost | yes (3-level split) | yes | no | no | no | no |
|
|
211
|
+
| Skill outcome testing | with/without A/B trials (experimental) | static LLM scoring | **yes, and more thoroughly**: Docker-isolated runs, script + LLM-rubric graders, weighted scores, CI pass-rate gates | yes (generate + eval, code agents) | no | no |
|
|
212
|
+
| MCP tool-definition cost | yes (3 serializations, `--anthropic` exact) | no | no | no | no | no |
|
|
213
|
+
| MCP quality rules | yes | no | no | no | no | no |
|
|
214
|
+
| 2026-07-28 readiness | migration diff: what breaks and how to fix | no | no | no | yes/no verdict | official test suite |
|
|
215
|
+
| CI budget gate + badge | yes (token budget) | no | pass-rate threshold | no | no | no |
|
|
216
|
+
|
|
217
|
+
Read that table honestly: on skill outcome testing, `skillgrade` is the more
|
|
218
|
+
serious tool and we would use it over our own `efaimo test` for anything
|
|
219
|
+
depending on the result. Ours is one experimental subcommand in an audit CLI;
|
|
220
|
+
theirs is a testing framework. efaimo's claim is the row count, not any single
|
|
221
|
+
row.
|
|
222
|
+
|
|
223
|
+
[SkillsBench](https://github.com/benchflow-ai/skillsbench), whose paper this
|
|
224
|
+
README cites, is not in the table because it is a benchmark rather than a tool
|
|
225
|
+
you point at your own work: a fixed suite of tasks with packaged environments
|
|
226
|
+
and verifiers, extensible by contributing tasks upstream. Useful for the
|
|
227
|
+
question "do skills help in general", which is why we cite it, and not for
|
|
228
|
+
"does mine help", which is what `efaimo test` and `skillgrade` answer.
|
|
204
229
|
|
|
205
230
|
efaimo runs the official conformance suite for you (`check --conformance` on
|
|
206
231
|
http targets) and complements security scanners such as Snyk agent-scan and the
|
|
207
|
-
skills installer (`npx skills`) rather than replacing them.
|
|
232
|
+
skills installer (`npx skills`) rather than replacing them. If your server is
|
|
233
|
+
built on the TypeScript SDK, efaimo tells you what breaks and why, and
|
|
234
|
+
[`@modelcontextprotocol/codemod`](https://www.npmjs.com/package/@modelcontextprotocol/codemod)
|
|
235
|
+
rewrites the SDK v1 to v2 call sites for you. Diff, codemod, re-check.
|
|
236
|
+
|
|
237
|
+
### "Doesn't tool search already solve this?"
|
|
238
|
+
|
|
239
|
+
Partly, and that is the point. Anthropic ships a
|
|
240
|
+
[tool search tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool):
|
|
241
|
+
mark tools `defer_loading: true` and the model discovers schemas on demand
|
|
242
|
+
instead of carrying all of them, with discovered schemas appended so the prompt
|
|
243
|
+
cache survives. Where it applies, it is a better fix than anything a linter can
|
|
244
|
+
tell you, and efaimo's own rules point at it (E127 and E128 suggest deferred
|
|
245
|
+
loading as a remedy).
|
|
246
|
+
|
|
247
|
+
It does not make measurement unnecessary, for three reasons. It is opt-in per
|
|
248
|
+
tool, so somebody has to decide which tools are worth deferring, and that
|
|
249
|
+
decision needs the per-tool numbers. It is a client-side feature, so it says
|
|
250
|
+
nothing about what your server publishes to hosts that do not implement it.
|
|
251
|
+
And a mitigation you cannot measure is a mitigation you are trusting on faith:
|
|
252
|
+
`weigh` reports the definitions your server actually ships, which is the input
|
|
253
|
+
every mitigation operates on.
|
|
254
|
+
|
|
255
|
+
If deferred loading covers your whole setup, you may not need this tool. Run
|
|
256
|
+
`weigh` once and find out; that answer is worth more than our opinion.
|
|
208
257
|
|
|
209
258
|
## Install
|
|
210
259
|
|
|
@@ -222,6 +271,9 @@ Or add it to a project with `npm i -D efaimo`, or the GitHub Action:
|
|
|
222
271
|
command: check --skill ./skills --strict
|
|
223
272
|
```
|
|
224
273
|
|
|
274
|
+
The Action is listed on the
|
|
275
|
+
[GitHub Marketplace](https://github.com/marketplace/actions/efaimo).
|
|
276
|
+
|
|
225
277
|
Gate a pull request on context-window growth, too:
|
|
226
278
|
|
|
227
279
|
```bash
|
|
@@ -243,11 +295,16 @@ More recipes (pre-commit, GitLab, editor audit, programmatic use):
|
|
|
243
295
|
|---|---|
|
|
244
296
|
| **S101 to S106** | skills: frontmatter and trigger quality, trigger collisions, context budget, reference integrity, injection hygiene |
|
|
245
297
|
| **E101 to E118** | MCP 2026-07-28 readiness: deprecated primitives, statelessness, `server/discover`, `resultType`, cache fields, transport |
|
|
246
|
-
| **E121 to E130** | MCP quality: description quality, annotations, schema hygiene, tool-count and token-cost budgets |
|
|
298
|
+
| **E121 to E128, E130** | MCP quality: description quality, annotations, schema hygiene, tool-count and token-cost budgets |
|
|
299
|
+
|
|
300
|
+
`E000` is not a finding about your server: it means one of efaimo's own rules
|
|
301
|
+
threw and was skipped, so the report is incomplete. It is never scored.
|
|
247
302
|
|
|
248
303
|
Quality (E12x-E13x) and skill (S) findings set the letter grade; readiness
|
|
249
|
-
findings (E101-E118) are reported as an ungraded migration diff
|
|
250
|
-
|
|
304
|
+
findings (E101-E118) are reported as an ungraded migration diff, which
|
|
305
|
+
`--strict-readiness` can turn into a non-zero exit
|
|
306
|
+
without touching the grade.
|
|
307
|
+
Every finding carries a stable id you can suppress or link. See
|
|
251
308
|
[docs/RULES.md](./docs/RULES.md).
|
|
252
309
|
|
|
253
310
|
## Roadmap
|
|
@@ -255,8 +312,9 @@ ratifies. Every finding carries a stable id you can suppress or link. See
|
|
|
255
312
|
- Harden `efaimo test`: a separately chosen judge model, confidence intervals on
|
|
256
313
|
the delta, multi-turn tool-use trials, and judge calibration, so the
|
|
257
314
|
experimental harness earns unqualified trust.
|
|
258
|
-
-
|
|
259
|
-
|
|
315
|
+
- Readiness rules for the two 2026-07-28 changes that have none yet: the
|
|
316
|
+
`Mcp-Method` / `Mcp-Name` request headers (SEP-2243), and Tasks moving out of
|
|
317
|
+
core into the `io.modelcontextprotocol/tasks` extension (SEP-2663).
|
|
260
318
|
- A public, continuously updated Agent Skills Quality Index over a broad corpus.
|
|
261
319
|
|
|
262
320
|
## Stability
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: efaimo
|
|
|
3
3
|
description: Audit MCP servers and Agent Skills for quality and context-window cost. Use when the user wants to check an MCP server's token cost, lint a skill, verify 2026-07-28 MCP spec readiness, or add an efaimo badge or CI gate.
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.1.
|
|
6
|
+
version: "0.1.1"
|
|
7
7
|
homepage: "https://efaimo.ai"
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -18,6 +18,18 @@ skill well-formed", or "add a CI gate / badge for context cost".
|
|
|
18
18
|
|
|
19
19
|
Run via `npx efaimo` (no install needed).
|
|
20
20
|
|
|
21
|
+
**Before running any of these against a stdio server: auditing one means
|
|
22
|
+
executing it.** `weigh` and `check --mcp` start the command you pass as a child
|
|
23
|
+
process and speak MCP to it, because that is the only way to see the tool
|
|
24
|
+
definitions it would put in a context window. So only point them at commands
|
|
25
|
+
you would be willing to run yourself. A remote target (`https://...`) is not
|
|
26
|
+
executed and is the safe option for something you do not trust. `--client`
|
|
27
|
+
reads the MCP config files in the current directory as well as your user
|
|
28
|
+
config, so running it inside a repository you did not write means starting the
|
|
29
|
+
servers that repository configured. This warning lives in `SECURITY.md` and did
|
|
30
|
+
not live here, which is the wrong way round: the policy is where humans look,
|
|
31
|
+
and this file is where agents take orders.
|
|
32
|
+
|
|
21
33
|
- **Weigh context cost of an MCP server**
|
|
22
34
|
`npx efaimo weigh "npx -y <server-package>"` for a stdio server, or
|
|
23
35
|
`npx efaimo weigh https://host/mcp` for a remote one. Add `--json` for machine
|
package/dist/check/check.js
CHANGED
|
@@ -10,8 +10,12 @@ import { findSkills } from "../skills/parse.js";
|
|
|
10
10
|
import { weighServer, weighSkills } from "../weigh/weigh.js";
|
|
11
11
|
import { VERSION } from "../version.js";
|
|
12
12
|
function buildReport(surface, target, findings, notes) {
|
|
13
|
-
// MCP readiness findings (E101-E118) are a migration diff for
|
|
14
|
-
// not
|
|
13
|
+
// MCP readiness findings (E101-E118) are a migration diff for the 2026-07-28
|
|
14
|
+
// spec: a migration not yet made is a to-do list, not a defect in what
|
|
15
|
+
// shipped. Reported separately and never graded (ADR-014, reaffirmed in
|
|
16
|
+
// ADR-027). That decision is CLOSED: the spec published on 2026-07-28 and
|
|
17
|
+
// readiness still does not count toward a grade. This comment described it
|
|
18
|
+
// as open for a day after ADR-027, in the file that implements the split.
|
|
15
19
|
const readinessAll = [];
|
|
16
20
|
const gradedAll = [];
|
|
17
21
|
for (const f of findings) {
|
package/dist/cli.js
CHANGED
|
@@ -7,9 +7,11 @@ import { VERSION } from "./version.js";
|
|
|
7
7
|
import { resolveTarget } from "./targets/resolve.js";
|
|
8
8
|
import { loadClientServers, SUPPORTED_CLIENTS } from "./targets/clientConfigs.js";
|
|
9
9
|
import { introspectServer } from "./clients/introspect.js";
|
|
10
|
+
import { SPEC_VERSION } from "./clients/rawprobe.js";
|
|
10
11
|
import { weighServer, weighSkills } from "./weigh/weigh.js";
|
|
11
12
|
import { findSkills } from "./skills/parse.js";
|
|
12
13
|
import { diffServerWeigh } from "./weigh/diff.js";
|
|
14
|
+
import { DEFAULT_CONTEXT_WINDOW, formatWindowShare, setContextWindow } from "./weigh/window.js";
|
|
13
15
|
import { checkMcpRepoOnly, checkMcpTarget, checkSkillSet } from "./check/check.js";
|
|
14
16
|
import { renderCheckPretty, renderDiffPretty, renderScenarioPlan, renderServerWeighPretty, renderSkillSetPretty, renderSkillWeighPretty, renderTestReportPretty, setColor, } from "./reporters/pretty.js";
|
|
15
17
|
import { parseScenario, runScenario } from "./testing/harness.js";
|
|
@@ -45,7 +47,21 @@ function collectPairs(sep) {
|
|
|
45
47
|
};
|
|
46
48
|
}
|
|
47
49
|
function colorSetup(opts) {
|
|
48
|
-
|
|
50
|
+
// Escape codes are noise once stdout is a file or another tool's stdin, and
|
|
51
|
+
// `efaimo weigh > report.txt` is a normal thing to do. So colour needs both
|
|
52
|
+
// the user's consent and a terminal to write to; FORCE_COLOR is the escape
|
|
53
|
+
// hatch for CI that renders ANSI in its log viewer.
|
|
54
|
+
const forced = process.env.FORCE_COLOR !== undefined && process.env.FORCE_COLOR !== "0";
|
|
55
|
+
const wanted = opts.color !== false && process.env.NO_COLOR === undefined;
|
|
56
|
+
setColor(wanted && (forced || process.stdout.isTTY === true));
|
|
57
|
+
}
|
|
58
|
+
function windowSetup(opts) {
|
|
59
|
+
if (opts.window === undefined)
|
|
60
|
+
return;
|
|
61
|
+
const n = Number(opts.window);
|
|
62
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
63
|
+
fail(`--window must be a positive number of tokens, got "${opts.window}"`);
|
|
64
|
+
setContextWindow(n);
|
|
49
65
|
}
|
|
50
66
|
function fail(message) {
|
|
51
67
|
console.error(pc.red(`error: ${message}`));
|
|
@@ -100,9 +116,11 @@ program
|
|
|
100
116
|
.option("--allow-increase <pct>", "with --diff: exit 1 if the increase exceeds pct percent")
|
|
101
117
|
.option("--badge [file]", "write an SVG badge + shields endpoint JSON")
|
|
102
118
|
.option("--anthropic [model]", "also measure exact Claude tokens via the count_tokens API (needs ANTHROPIC_API_KEY)")
|
|
119
|
+
.option("--window <tokens>", "context window the share is reported against", String(DEFAULT_CONTEXT_WINDOW))
|
|
103
120
|
.option("--no-color", "disable colors")
|
|
104
121
|
.action(async (targetArg, opts) => {
|
|
105
122
|
colorSetup(opts);
|
|
123
|
+
windowSetup(opts);
|
|
106
124
|
const timeoutMs = seconds(opts.timeout) * 1000;
|
|
107
125
|
const maxTokens = parseNumberOpt(opts.maxTokens, "--max-tokens");
|
|
108
126
|
const allowIncreasePct = parseNumberOpt(opts.allowIncrease, "--allow-increase");
|
|
@@ -114,6 +132,29 @@ program
|
|
|
114
132
|
fail(`no MCP servers found for client "${opts.client}" (checked: ${[...conf.sources, ...conf.missing].join(", ")})`);
|
|
115
133
|
}
|
|
116
134
|
console.error(pc.dim(`config: ${conf.sources.join(", ")}`));
|
|
135
|
+
// Say what is about to be EXECUTED, not just which file it came from.
|
|
136
|
+
//
|
|
137
|
+
// --client reads .mcp.json, .cursor/mcp.json and .vscode/mcp.json from
|
|
138
|
+
// the CURRENT DIRECTORY as well as the user config, and then spawns
|
|
139
|
+
// every command in them. Cloning an untrusted repo and running the audit
|
|
140
|
+
// tool inside it was therefore arbitrary code execution, and the only
|
|
141
|
+
// thing printed was the friendly config key, never the command. Cursor
|
|
142
|
+
// and VS Code gate project-scoped MCP config behind workspace trust;
|
|
143
|
+
// this printed nothing at all.
|
|
144
|
+
//
|
|
145
|
+
// Listing them is the minimum. A stdio entry names a real command that
|
|
146
|
+
// is about to run on this machine.
|
|
147
|
+
for (const e of conf.entries) {
|
|
148
|
+
const t = e.target;
|
|
149
|
+
const shown = t.kind === "stdio"
|
|
150
|
+
? [t.command, ...t.args].join(" ") + (t.env && Object.keys(t.env).length ? ` (+${Object.keys(t.env).length} env)` : "")
|
|
151
|
+
: t.url;
|
|
152
|
+
console.error(pc.dim(` ${e.name}: ${shown}`));
|
|
153
|
+
}
|
|
154
|
+
if (conf.fromCwd.length) {
|
|
155
|
+
console.error(pc.yellow(` ! ${conf.fromCwd.length} of these came from the current directory (${conf.fromCwd.join(", ")}), not your user config.\n` +
|
|
156
|
+
` Auditing a stdio server means executing it. Only continue in a repository you trust.`));
|
|
157
|
+
}
|
|
117
158
|
targets.push(...conf.entries.map((e) => e.target));
|
|
118
159
|
}
|
|
119
160
|
if (targetArg) {
|
|
@@ -186,7 +227,7 @@ program
|
|
|
186
227
|
.filter((r) => r.kind === "mcp")
|
|
187
228
|
.reduce((s, r) => s + r.totals.claudeStyle, 0);
|
|
188
229
|
if (mcpTotals > 0) {
|
|
189
|
-
console.log(pc.bold(`\ncombined MCP context cost (Claude-style, o200k est.): ${mcpTotals.toLocaleString("en-US")} tokens (
|
|
230
|
+
console.log(pc.bold(`\ncombined MCP context cost (Claude-style, o200k est.): ${mcpTotals.toLocaleString("en-US")} tokens (${formatWindowShare(mcpTotals)})`));
|
|
190
231
|
}
|
|
191
232
|
}
|
|
192
233
|
}
|
|
@@ -226,6 +267,7 @@ program
|
|
|
226
267
|
.option("--repo <path>", "additionally scan this source repo for deprecated API usage")
|
|
227
268
|
.option("--no-probe", "skip live readiness probes (bare request, server/discover, ordering)")
|
|
228
269
|
.option("--strict", "exit 1 on warnings too")
|
|
270
|
+
.option("--strict-readiness", "exit 1 if the 2026-07-28 migration diff is not clean (never changes the grade)")
|
|
229
271
|
.option("--conformance", "after the audit, run the official MCP conformance suite (http targets)")
|
|
230
272
|
.option("--header <header>", 'HTTP header "Key: Value" (repeatable)', collectPairs(":"))
|
|
231
273
|
.option("--env <pair>", "KEY=VALUE for stdio servers (repeatable)", collectPairs("="))
|
|
@@ -234,9 +276,11 @@ program
|
|
|
234
276
|
.option("--md", "print Markdown")
|
|
235
277
|
.option("--badge [file]", "write a grade badge SVG + shields endpoint JSON")
|
|
236
278
|
.option("--anthropic [model]", "use exact Claude token counts where relevant")
|
|
279
|
+
.option("--window <tokens>", "context window the share is reported against", String(DEFAULT_CONTEXT_WINDOW))
|
|
237
280
|
.option("--no-color", "disable colors")
|
|
238
281
|
.action(async (targetArg, opts) => {
|
|
239
282
|
colorSetup(opts);
|
|
283
|
+
windowSetup(opts);
|
|
240
284
|
if (!targetArg)
|
|
241
285
|
fail("nothing to check: pass a target (see --help)");
|
|
242
286
|
if (opts.mcp && opts.skill)
|
|
@@ -305,10 +349,31 @@ program
|
|
|
305
349
|
console.error(pc.yellow("note: the official conformance suite drives http targets (--url); skipping for this target"));
|
|
306
350
|
}
|
|
307
351
|
else {
|
|
308
|
-
|
|
352
|
+
// The `latest` line of the conformance suite (0.1.16) predates the
|
|
353
|
+
// 2026-07-28 revision, so it silently tests the old protocol. Those
|
|
354
|
+
// scenarios ship on the `alpha` line only; --spec-version scopes them
|
|
355
|
+
// to the revision efaimo is about.
|
|
356
|
+
//
|
|
357
|
+
// Pinned to an exact version, because a dist-tag is not a pin. `alpha`
|
|
358
|
+
// moved from 0.2.0-alpha.9 to 0.2.0-alpha.10 on 2026-07-27, and we went
|
|
359
|
+
// on reporting "the official conformance suite" for a build nobody here
|
|
360
|
+
// had run: the same "we do not know what we ran" bug this block was
|
|
361
|
+
// written to fix, through the other door. Bumping it means re-reading
|
|
362
|
+
// `server --help` first; the two flags below were confirmed against
|
|
363
|
+
// 0.2.0-alpha.10.
|
|
364
|
+
const args = [
|
|
365
|
+
"-y",
|
|
366
|
+
"@modelcontextprotocol/conformance@0.2.0-alpha.10",
|
|
367
|
+
"server",
|
|
368
|
+
"--url",
|
|
369
|
+
target.url,
|
|
370
|
+
"--spec-version",
|
|
371
|
+
SPEC_VERSION,
|
|
372
|
+
];
|
|
373
|
+
console.error(pc.dim(`\nrunning official MCP conformance suite:\n npx ${args.slice(1).join(" ")}\n`));
|
|
309
374
|
// cross-spawn resolves npx(.cmd) without a shell, so target.url is passed
|
|
310
375
|
// as a literal argument (never interpreted by cmd.exe).
|
|
311
|
-
const r = crossSpawn.sync("npx",
|
|
376
|
+
const r = crossSpawn.sync("npx", args, {
|
|
312
377
|
stdio: "inherit",
|
|
313
378
|
});
|
|
314
379
|
if (r.status !== 0)
|
|
@@ -318,6 +383,15 @@ program
|
|
|
318
383
|
if (report.counts.error > 0 || (opts.strict && report.counts.warn > 0)) {
|
|
319
384
|
process.exitCode = 1;
|
|
320
385
|
}
|
|
386
|
+
// Readiness stays out of the grade, the badge and the default exit code
|
|
387
|
+
// (ADR-014, reaffirmed in ADR-027). But a team that HAS migrated had no way
|
|
388
|
+
// to stay migrated: --strict covers quality only, so nothing in CI could
|
|
389
|
+
// catch a regression back onto the legacy handshake. This is the opt-in for
|
|
390
|
+
// exactly that, and it moves the exit code only. Nobody's grade changes,
|
|
391
|
+
// no published number moves, and the operator decides rather than us.
|
|
392
|
+
if (opts.strictReadiness && (report.readiness?.findings.length ?? 0) > 0) {
|
|
393
|
+
process.exitCode = 1;
|
|
394
|
+
}
|
|
321
395
|
});
|
|
322
396
|
program
|
|
323
397
|
.command("test")
|
|
@@ -6,7 +6,14 @@ import type { ResolvedTarget } from "../targets/resolve.js";
|
|
|
6
6
|
* stateless requests, server/discover) need raw transport access.
|
|
7
7
|
* Every probe fails soft into a { skipped } marker.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The MCP revision efaimo audits against. It was called RC_VERSION until
|
|
11
|
+
* 2026-08-01, by which point the revision had been published for four days and
|
|
12
|
+
* the name had been lying for all of them. scripts/spec-drift.mjs reads this
|
|
13
|
+
* declaration by name, so renaming it again means updating that regex, which
|
|
14
|
+
* fails loudly rather than silently watching nothing.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SPEC_VERSION = "2026-07-28";
|
|
10
17
|
interface RpcReply {
|
|
11
18
|
result?: Record<string, unknown>;
|
|
12
19
|
error?: {
|
|
@@ -16,7 +23,7 @@ interface RpcReply {
|
|
|
16
23
|
timeout?: boolean;
|
|
17
24
|
procExit?: number | null;
|
|
18
25
|
}
|
|
19
|
-
export declare function
|
|
26
|
+
export declare function specMeta(): Record<string, unknown>;
|
|
20
27
|
/**
|
|
21
28
|
* Only an explicit "not initialized" / "session required" error proves the
|
|
22
29
|
* server requires the removed initialize handshake. A plain timeout is NOT proof
|
|
@@ -24,11 +31,20 @@ export declare function rcMeta(): Record<string, unknown>;
|
|
|
24
31
|
* inconclusive; a crash is its own signal. This keeps E105 from false-flagging a
|
|
25
32
|
* healthy but slow server, the worst verdict a readiness checker can emit.
|
|
26
33
|
*
|
|
27
|
-
* Detection is by message, not by error code
|
|
34
|
+
* Detection is by message, not by error code, and the spec that published on
|
|
35
|
+
* 2026-07-28 is the argument for why. The codes SDKs use here are
|
|
28
36
|
* implementation-defined (TS StreamableHTTP sends -32000 for both "Server not
|
|
29
37
|
* initialized" and "Mcp-Session-Id header is required", -32001 for "Session not
|
|
30
|
-
* found"),
|
|
31
|
-
*
|
|
38
|
+
* found"), and the MCP-defined ones MOVED between the locked RC and the
|
|
39
|
+
* published spec: MissingRequiredClientCapability went -32003 to -32021 and
|
|
40
|
+
* UnsupportedProtocolVersion went -32004 to -32022, and HeaderMismatch moved
|
|
41
|
+
* the same way, -32001 to -32020. This comment said HeaderMismatch was "added
|
|
42
|
+
* alongside them", which was wrong: the RC already defined it at -32001 in the
|
|
43
|
+
* transport prose, and what the published revision added was its entry in the
|
|
44
|
+
* schema. The distinction matters to anyone still returning -32001.
|
|
45
|
+
* -32002 (resource not found) is retired outright,
|
|
46
|
+
* replaced by -32602 and promised never to be reused. Matching on numbers
|
|
47
|
+
* would have rotted here on 2026-07-28; matching on messages did not.
|
|
32
48
|
*/
|
|
33
49
|
export declare function looksLikeInitGate(o: ProbeOutcome): boolean;
|
|
34
50
|
export declare function runProbes(target: Extract<ResolvedTarget, {
|