droid-mode 0.0.8 → 0.0.10
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
CHANGED
|
@@ -67,10 +67,14 @@ Droid Mode eliminates schema overhead by keeping tool definitions out of the LLM
|
|
|
67
67
|
|
|
68
68
|
| Metric | Native MCP | Droid Mode |
|
|
69
69
|
|--------|------------|------------|
|
|
70
|
-
| Schema overhead (3 tools) | 329 tokens | **0 tokens** |
|
|
70
|
+
| Schema overhead (3 tools used) | 329 tokens | **0 tokens** |
|
|
71
|
+
| Full server loaded (22 tools) | 2,400 tokens | **0 tokens** |
|
|
72
|
+
| Typical setup (5 servers, ~100 tools) | ~11,000 tokens | **0 tokens** |
|
|
71
73
|
| Total tokens (3-tool session) | 1,072 | **897** |
|
|
72
74
|
| **Savings** | — | **16%** |
|
|
73
75
|
|
|
76
|
+
Industry reports show MCP tools consuming 20-30% of context windows before any work begins. With Droid Mode, that overhead drops to zero — tools are loaded only when called.
|
|
77
|
+
|
|
74
78
|
Schema cost scales with tool count:
|
|
75
79
|
|
|
76
80
|
| Tools Enabled | Native MCP Cost | Droid Mode Cost |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
-
import { ensureDir, nowIsoCompact, safeIdentifier, writeJson, getDroidModeDataDir } from "./util.mjs";
|
|
3
|
+
import { ensureDir, nowIsoCompact, safeIdentifier, writeJson, getDroidModeDataDir, serverNameToDirName } from "./util.mjs";
|
|
4
4
|
|
|
5
5
|
/** @param {string} s */
|
|
6
6
|
function toPascalCase(s) {
|
|
@@ -89,9 +89,10 @@ function schemaToTs(schema, depth = 0) {
|
|
|
89
89
|
*/
|
|
90
90
|
export function hydrateTools(opts) {
|
|
91
91
|
const ts = nowIsoCompact();
|
|
92
|
+
const serverDir = serverNameToDirName(opts.serverName);
|
|
92
93
|
const baseOut =
|
|
93
94
|
opts.outDir ||
|
|
94
|
-
path.join(getDroidModeDataDir(), "hydrated",
|
|
95
|
+
path.join(getDroidModeDataDir(), "hydrated", serverDir, ts);
|
|
95
96
|
|
|
96
97
|
ensureDir(baseOut);
|
|
97
98
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import vm from "node:vm";
|
|
4
|
-
import { nowIsoCompact, sha256Hex, safeIdentifier, ensureDir, getDroidModeDataDir, writeJson } from "./util.mjs";
|
|
4
|
+
import { nowIsoCompact, sha256Hex, safeIdentifier, ensureDir, getDroidModeDataDir, writeJson, serverNameToDirName } from "./util.mjs";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Static, best-effort disallow list for sandboxed workflows.
|
|
@@ -200,10 +200,12 @@ export async function executeWorkflow(opts) {
|
|
|
200
200
|
export function writeRunArtifact(opts) {
|
|
201
201
|
const dataDir = getDroidModeDataDir();
|
|
202
202
|
const ts = nowIsoCompact();
|
|
203
|
-
const
|
|
203
|
+
const serverDir = serverNameToDirName(opts.serverName);
|
|
204
|
+
const outDir = path.join(dataDir, "runs", serverDir, ts);
|
|
204
205
|
ensureDir(outDir);
|
|
205
206
|
const payload = {
|
|
206
207
|
serverName: opts.serverName,
|
|
208
|
+
serverDir,
|
|
207
209
|
workflowPath: opts.workflowPath,
|
|
208
210
|
tools: opts.tools,
|
|
209
211
|
finishedAt: new Date().toISOString(),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { getDroidModeDataDir, readJsonFileIfExists, writeJson } from "./util.mjs";
|
|
2
|
+
import { getDroidModeDataDir, readJsonFileIfExists, writeJson, serverNameToDirName } from "./util.mjs";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @param {import("./mcp_client.mjs").McpClient} client
|
|
@@ -27,7 +27,8 @@ export async function fetchAllTools(client) {
|
|
|
27
27
|
*/
|
|
28
28
|
export async function getToolsCached(opts) {
|
|
29
29
|
const dataDir = getDroidModeDataDir();
|
|
30
|
-
const
|
|
30
|
+
const serverDir = serverNameToDirName(opts.serverName);
|
|
31
|
+
const cacheFile = path.join(dataDir, "cache", serverDir, "tools.json");
|
|
31
32
|
const maxAgeMs = typeof opts.maxAgeMs === "number" ? opts.maxAgeMs : 30 * 60 * 1000; // 30 minutes
|
|
32
33
|
|
|
33
34
|
if (!opts.refresh) {
|
|
@@ -46,6 +47,7 @@ export async function getToolsCached(opts) {
|
|
|
46
47
|
const payload = {
|
|
47
48
|
fetchedAt: new Date().toISOString(),
|
|
48
49
|
serverName: opts.serverName,
|
|
50
|
+
serverDir,
|
|
49
51
|
protocolVersion: opts.client.negotiatedProtocolVersion,
|
|
50
52
|
serverInfo: opts.client.serverInfo,
|
|
51
53
|
capabilities: opts.client.serverCapabilities,
|
|
@@ -17,6 +17,25 @@ export function sha256Hex(s) {
|
|
|
17
17
|
return crypto.createHash("sha256").update(s).digest("hex");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Convert an arbitrary server name into a safe directory segment.
|
|
22
|
+
* Always sanitizes and appends hash suffix for safety.
|
|
23
|
+
* @param {string} serverName
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function serverNameToDirName(serverName) {
|
|
27
|
+
const raw = String(serverName ?? "");
|
|
28
|
+
const hash = sha256Hex(raw).slice(0, 8);
|
|
29
|
+
const base = raw
|
|
30
|
+
.replace(/[/\\]/g, "_") // block path separators
|
|
31
|
+
.replace(/\.\./g, "_") // block traversal
|
|
32
|
+
.replace(/[^a-zA-Z0-9._-]/g, "_") // whitelist safe chars
|
|
33
|
+
.replace(/^\.+/g, "") // avoid dotfiles
|
|
34
|
+
.slice(0, 40);
|
|
35
|
+
|
|
36
|
+
return `${base || "server"}-${hash}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
20
39
|
/**
|
|
21
40
|
* Convert a tool name (often snake_case or kebab-case) to a safe camelCase identifier.
|
|
22
41
|
* @param {string} toolName
|