efaimo 0.4.0 → 0.5.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/CHANGELOG.md +48 -0
- package/SKILL.md +1 -1
- package/dist/clients/introspect.js +4 -4
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +71 -65
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,53 @@ 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
|
+
## [0.5.0] - 2026-09-04
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **`efaimo mcp` is built on the 2.x SDK line and now satisfies the
|
|
12
|
+
specification this tool audits other servers against.** The 1.x line speaks
|
|
13
|
+
2025-11-25; it cannot answer `server/discover` and cannot carry the SEP-2549
|
|
14
|
+
cache fields, so the one MCP server this project ships failed two MUST-level
|
|
15
|
+
items of the 2026-07-28 revision, and `check --mcp` said so about it on every
|
|
16
|
+
run. The skill this project publishes about that migration opens with "upgrade
|
|
17
|
+
the SDK first". This is that.
|
|
18
|
+
|
|
19
|
+
Measured against the built server, before and after, with the same command:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
before 2026-07-28 readiness 3 items to migrate E106, E118, E107
|
|
23
|
+
after 2026-07-28 readiness clean, nothing to migrate
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Read off the wire rather than from our own tool as well: a modern opening now
|
|
27
|
+
answers `server/discover` with `supportedVersions`, `capabilities`,
|
|
28
|
+
`resultType: "complete"`, `ttlMs` and `cacheScope`, and `tools/list` carries
|
|
29
|
+
the same three fields. A 2025-era client that opens with `initialize` is still
|
|
30
|
+
served, unchanged, because `serveStdio` pins the era per connection rather
|
|
31
|
+
than making the server pick one.
|
|
32
|
+
|
|
33
|
+
- **The client half moved with it**, from `@modelcontextprotocol/sdk` to
|
|
34
|
+
`@modelcontextprotocol/client`. Doing only the server half would have left
|
|
35
|
+
both generations installed, and E101 stops firing the moment any 2.x package
|
|
36
|
+
is present, so the half-migration would have silenced the rule that was
|
|
37
|
+
telling the truth.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **Two moderate advisories, by subtraction.** `qs` arrived through
|
|
42
|
+
`body-parser` through `express` through the 1.x SDK, which pulls a whole HTTP
|
|
43
|
+
and OAuth stack that a stdio server never speaks. `pnpm audit --prod` now
|
|
44
|
+
reports no known vulnerabilities, and production dependencies went from 30 to
|
|
45
|
+
13.
|
|
46
|
+
|
|
47
|
+
### Notes
|
|
48
|
+
|
|
49
|
+
- No rule changed, so no grade moved. `rulesVersion` is untouched and the
|
|
50
|
+
published skills index does not need regenerating.
|
|
51
|
+
- `check --skill`, `weigh`, `find` and `test` are byte-identical in behaviour;
|
|
52
|
+
the only thing that moved is what `efaimo mcp` puts on the wire.
|
|
53
|
+
|
|
7
54
|
## [0.4.0] - 2026-09-03
|
|
8
55
|
|
|
9
56
|
### Fixed
|
|
@@ -408,6 +455,7 @@ First release.
|
|
|
408
455
|
Documented rule set (`docs/RULES.md`), token methodology (`docs/METHODOLOGY.md`),
|
|
409
456
|
and integration guide (`docs/INTEGRATIONS.md`).
|
|
410
457
|
|
|
458
|
+
[0.5.0]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.5.0
|
|
411
459
|
[0.4.0]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.4.0
|
|
412
460
|
[0.3.0]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.3.0
|
|
413
461
|
[0.2.0]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.2.0
|
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, find out whether a search can single out a server's tools under deferred tool loading, or add an efaimo badge or CI gate.
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.5.0"
|
|
7
7
|
homepage: "https://efaimo.ai"
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import crossSpawn from "cross-spawn";
|
|
2
|
-
import { Client } from "@modelcontextprotocol/
|
|
3
|
-
import { StdioClientTransport } from "@modelcontextprotocol/
|
|
4
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/
|
|
5
|
-
import { SSEClientTransport } from "@modelcontextprotocol/
|
|
2
|
+
import { Client } from "@modelcontextprotocol/client";
|
|
3
|
+
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
4
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
5
|
+
import { SSEClientTransport } from "@modelcontextprotocol/client";
|
|
6
6
|
import { introspectStateless } from "./stateless.js";
|
|
7
7
|
import { minimalChildEnv } from "../util/childEnv.js";
|
|
8
8
|
import { truncate, withTimeout } from "../util/misc.js";
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function buildMcpServer():
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
export declare function buildMcpServer(): McpServer;
|
|
3
3
|
export declare function runMcpServer(): Promise<void>;
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,88 +1,94 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
3
|
+
import { z } from "zod";
|
|
4
4
|
import { checkSkillSet } from "../check/check.js";
|
|
5
5
|
import { weighSkills } from "../weigh/weigh.js";
|
|
6
6
|
import { findSkills } from "../skills/parse.js";
|
|
7
7
|
import { renderCheckMarkdown, renderSkillSetMarkdown, renderWeighMarkdown } from "../reporters/markdown.js";
|
|
8
8
|
import { VERSION } from "../version.js";
|
|
9
|
+
/**
|
|
10
|
+
* `efaimo mcp`: a small, read-only MCP server that exposes the skill checks to an
|
|
11
|
+
* agent, so it can lint or weigh a skill mid-session before committing it to
|
|
12
|
+
* context. Deliberately narrow: the tools only read files. They spawn no process
|
|
13
|
+
* and open no socket (unlike `check --mcp`, which connects to a live server), and
|
|
14
|
+
* `test` is not exposed at all because it spends tokens. That keeps the surface
|
|
15
|
+
* safe for an agent to call unattended.
|
|
16
|
+
*
|
|
17
|
+
* Built on the 2.x SDK line since 0.5.0. The 1.x line speaks 2025-11-25 and
|
|
18
|
+
* cannot answer `server/discover` or carry the SEP-2549 cache fields, so this
|
|
19
|
+
* server failed two MUST-level items of the specification the rest of this tool
|
|
20
|
+
* audits other servers against, and the skill this project publishes about that
|
|
21
|
+
* migration opens with "upgrade the SDK first". It was the only server we ship
|
|
22
|
+
* and the only one we had not migrated.
|
|
23
|
+
*
|
|
24
|
+
* `serveStdio` owns the era decision rather than this file: the opening exchange
|
|
25
|
+
* selects it, one instance is pinned for the connection, and a 2025-era client
|
|
26
|
+
* that opens with `initialize` is still served. Registering the tools once, on a
|
|
27
|
+
* factory that serves both eras, is the whole reason to use it instead of
|
|
28
|
+
* wiring a transport by hand.
|
|
29
|
+
*/
|
|
9
30
|
// Both tools only read local files, so the same hints apply: safe to call without
|
|
10
31
|
// a confirmation, safe to repeat, and not reaching out to any external service.
|
|
11
32
|
const READ_ONLY = { readOnlyHint: true, idempotentHint: true, openWorldHint: false };
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
// One shared shape. `.min(1)` is the schema saying what `requireString` used to
|
|
34
|
+
// say at runtime: an empty path is not a path. Under the 2.x registration the
|
|
35
|
+
// SDK validates against this before the handler runs, so the check moved from
|
|
36
|
+
// our code into the contract, which is where a client can also see it.
|
|
37
|
+
const pathInput = (what) => z.object({ path: z.string().min(1).describe(what) });
|
|
38
|
+
const CHECK_DESCRIPTION = "Lint an Agent Skill, or a folder of skills, for spec compliance, trigger quality, " +
|
|
39
|
+
"context-window cost, reference integrity, and injection hygiene. Returns a grade from " +
|
|
40
|
+
"A to F and the findings. Read-only: reads files only.";
|
|
41
|
+
const WEIGH_DESCRIPTION = "Measure the context-window token cost of an Agent Skill: the metadata loaded into every " +
|
|
42
|
+
"session and the body loaded when the skill triggers. Returns token counts per skill. " +
|
|
43
|
+
"Read-only: reads files only.";
|
|
44
|
+
const text = (s) => ({ content: [{ type: "text", text: s }] });
|
|
45
|
+
const failure = (e) => ({
|
|
46
|
+
content: [{ type: "text", text: `error: ${e instanceof Error ? e.message : String(e)}` }],
|
|
47
|
+
isError: true,
|
|
24
48
|
});
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
description:
|
|
29
|
-
|
|
30
|
-
"A to F and the findings. Read-only: reads files only.",
|
|
31
|
-
inputSchema: pathSchema("Path to a SKILL.md file or a directory that contains skills."),
|
|
49
|
+
export function buildMcpServer() {
|
|
50
|
+
const server = new McpServer({ name: "efaimo", version: VERSION }, { capabilities: { tools: {} } });
|
|
51
|
+
server.registerTool("efaimo_check_skill", {
|
|
52
|
+
description: CHECK_DESCRIPTION,
|
|
53
|
+
inputSchema: pathInput("Path to a SKILL.md file or a directory that contains skills."),
|
|
32
54
|
annotations: READ_ONLY,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const res = await checkSkillSet(
|
|
55
|
+
}, async ({ path }) => {
|
|
56
|
+
try {
|
|
57
|
+
const res = await checkSkillSet(path, path);
|
|
36
58
|
if (res.perSkill.length === 1 && res.setFindings.length === 0) {
|
|
37
|
-
return renderCheckMarkdown(res.perSkill[0].report);
|
|
59
|
+
return text(renderCheckMarkdown(res.perSkill[0].report));
|
|
38
60
|
}
|
|
39
|
-
return renderSkillSetMarkdown(res);
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: "efaimo_weigh_skill",
|
|
44
|
-
description: "Measure the context-window token cost of an Agent Skill: the metadata loaded into every " +
|
|
45
|
-
"session and the body loaded when the skill triggers. Returns token counts per skill. " +
|
|
46
|
-
"Read-only: reads files only.",
|
|
47
|
-
inputSchema: pathSchema("Path to a SKILL.md file or a directory that contains skills."),
|
|
48
|
-
annotations: READ_ONLY,
|
|
49
|
-
async run(args) {
|
|
50
|
-
const p = requireString(args, "path");
|
|
51
|
-
const set = findSkills(p);
|
|
52
|
-
if (!set.skills.length)
|
|
53
|
-
throw new Error(`no SKILL.md found under "${p}"`);
|
|
54
|
-
return renderWeighMarkdown(await weighSkills(set));
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
];
|
|
58
|
-
export function buildMcpServer() {
|
|
59
|
-
const server = new Server({ name: "efaimo", version: VERSION }, { capabilities: { tools: {} } });
|
|
60
|
-
server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
61
|
-
tools: TOOLS.map((t) => ({
|
|
62
|
-
name: t.name,
|
|
63
|
-
description: t.description,
|
|
64
|
-
inputSchema: t.inputSchema,
|
|
65
|
-
annotations: t.annotations,
|
|
66
|
-
})),
|
|
67
|
-
}));
|
|
68
|
-
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
69
|
-
const tool = TOOLS.find((t) => t.name === req.params.name);
|
|
70
|
-
if (!tool) {
|
|
71
|
-
return { content: [{ type: "text", text: `unknown tool: ${req.params.name}` }], isError: true };
|
|
61
|
+
return text(renderSkillSetMarkdown(res));
|
|
72
62
|
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
return failure(e);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
server.registerTool("efaimo_weigh_skill", {
|
|
68
|
+
description: WEIGH_DESCRIPTION,
|
|
69
|
+
inputSchema: pathInput("Path to a SKILL.md file or a directory that contains skills."),
|
|
70
|
+
annotations: READ_ONLY,
|
|
71
|
+
}, async ({ path }) => {
|
|
73
72
|
try {
|
|
74
|
-
const
|
|
75
|
-
|
|
73
|
+
const set = findSkills(path);
|
|
74
|
+
if (!set.skills.length)
|
|
75
|
+
throw new Error(`no SKILL.md found under "${path}"`);
|
|
76
|
+
return text(renderWeighMarkdown(await weighSkills(set)));
|
|
76
77
|
}
|
|
77
78
|
catch (e) {
|
|
78
|
-
|
|
79
|
-
return { content: [{ type: "text", text: `error: ${message}` }], isError: true };
|
|
79
|
+
return failure(e);
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
82
|
return server;
|
|
83
83
|
}
|
|
84
84
|
export async function runMcpServer() {
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// A factory, not an instance: `serveStdio` pins one per connection after the
|
|
86
|
+
// opening exchange tells it which era it is talking to.
|
|
87
|
+
serveStdio(() => buildMcpServer(), {
|
|
88
|
+
onerror: (e) => console.error(`efaimo mcp: ${e.message}`),
|
|
89
|
+
});
|
|
87
90
|
console.error(`efaimo mcp v${VERSION}: read-only skill tools ready on stdio`);
|
|
91
|
+
// stdin holds the process open; there is nothing to await. Returning a promise
|
|
92
|
+
// that never settles would only make the shutdown path harder to read.
|
|
93
|
+
await new Promise(() => { });
|
|
88
94
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Kept in sync with package.json by test/meta.test.ts. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.5.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Kept in sync with package.json by test/meta.test.ts. */
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.5.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "efaimo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Audit what your agent loads: quality and context cost for MCP servers and Agent Skills",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -65,12 +65,14 @@
|
|
|
65
65
|
"prepublishOnly": "npm run typecheck && npm run build && npm run test"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@modelcontextprotocol/
|
|
68
|
+
"@modelcontextprotocol/client": "2.0.0",
|
|
69
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
69
70
|
"commander": "^15.0.0",
|
|
70
71
|
"cross-spawn": "^7.0.6",
|
|
71
72
|
"gpt-tokenizer": "^3.4.0",
|
|
72
73
|
"picocolors": "^1.1.1",
|
|
73
|
-
"yaml": "^2.9.0"
|
|
74
|
+
"yaml": "^2.9.0",
|
|
75
|
+
"zod": "^4.5.4"
|
|
74
76
|
},
|
|
75
77
|
"devDependencies": {
|
|
76
78
|
"@types/cross-spawn": "^6.0.6",
|