toolstile 0.3.0 → 0.4.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 +3 -3
- package/dist/api.d.ts +2 -0
- package/dist/cli.js +1 -1
- package/dist/commands.d.ts +2 -0
- package/dist/commands.js +28 -3
- package/dist/report.js +1 -1
- package/dist/state.d.ts +5 -1
- package/dist/state.js +19 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,7 +165,7 @@ values) and prints a ready-to-post link. To post the text report instead, add
|
|
|
165
165
|
|
|
166
166
|
## Connect your agents to Toolstile
|
|
167
167
|
|
|
168
|
-
If you use [Toolstile](https://toolstile.com), the CLI can also sign in and set
|
|
168
|
+
If you use [Toolstile](https://www.toolstile.com), the CLI can also sign in and set
|
|
169
169
|
up the agents on a machine for you:
|
|
170
170
|
|
|
171
171
|
```sh
|
|
@@ -211,7 +211,7 @@ dashboard.
|
|
|
211
211
|
|
|
212
212
|
| Option | Command | Meaning |
|
|
213
213
|
| --- | --- | --- |
|
|
214
|
-
| `--server <url>` | `auth login` | Toolstile address (default `https://toolstile.com`, or `TOOLSTILE_URL`) |
|
|
214
|
+
| `--server <url>` | `auth login` | Toolstile address (default `https://app.toolstile.com`, or `TOOLSTILE_URL`) |
|
|
215
215
|
| `--no-browser` | `auth login` | Do not try to open a browser on this machine |
|
|
216
216
|
| `--enable <agents>` | `mcp setup` | Turn these on without prompting (comma-separated) |
|
|
217
217
|
| `--disable <agents>` | `mcp setup` | Turn these off without prompting |
|
|
@@ -229,7 +229,7 @@ const { grade, findings } = audit();
|
|
|
229
229
|
## About
|
|
230
230
|
|
|
231
231
|
Built by [Akshit Kr Nagpal](https://akshit.io) as part of
|
|
232
|
-
[Toolstile](https://toolstile.com), an MCP gateway that keeps upstream
|
|
232
|
+
[Toolstile](https://www.toolstile.com), an MCP gateway that keeps upstream
|
|
233
233
|
credentials off your machines and lets you choose which tools each agent can use.
|
|
234
234
|
The audit works on its own and does not require a Toolstile account.
|
|
235
235
|
|
package/dist/api.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -68,7 +68,7 @@ login prints a link and a code. Open the link on any device with a browser
|
|
|
68
68
|
reach this machine, so it works over SSH and on servers without a browser.
|
|
69
69
|
|
|
70
70
|
Options
|
|
71
|
-
--server <url> Toolstile address (default https://toolstile.com, or TOOLSTILE_URL)
|
|
71
|
+
--server <url> Toolstile address (default https://app.toolstile.com, or TOOLSTILE_URL)
|
|
72
72
|
--no-browser Do not try to open a browser on this machine
|
|
73
73
|
`;
|
|
74
74
|
const MCP_HELP = `toolstile mcp: connect the agents on this machine to Toolstile
|
package/dist/commands.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export declare class UserError extends Error {
|
|
|
23
23
|
}
|
|
24
24
|
/** HTTPS, except a loopback server for local development. */
|
|
25
25
|
export declare function normalizeServer(value: string): string;
|
|
26
|
+
/** A gateway URL the server reported: HTTPS (or loopback HTTP) with no credentials, else undefined. */
|
|
27
|
+
export declare function gatewayUrl(value: unknown): string | undefined;
|
|
26
28
|
export declare function login(ctx: Context, options: {
|
|
27
29
|
server?: string;
|
|
28
30
|
browser: boolean;
|
package/dist/commands.js
CHANGED
|
@@ -20,13 +20,31 @@ export function normalizeServer(value) {
|
|
|
20
20
|
url = new URL(value);
|
|
21
21
|
}
|
|
22
22
|
catch {
|
|
23
|
-
throw new UserError(`--server must be a URL, for example https://toolstile.com`);
|
|
23
|
+
throw new UserError(`--server must be a URL, for example https://app.toolstile.com`);
|
|
24
24
|
}
|
|
25
25
|
const loopback = /^(localhost|127\.\d+\.\d+\.\d+|\[::1\])$/.test(url.hostname);
|
|
26
26
|
if (url.protocol !== "https:" && !(loopback && url.protocol === "http:"))
|
|
27
27
|
throw new UserError("--server must use https://");
|
|
28
28
|
return url.origin;
|
|
29
29
|
}
|
|
30
|
+
/** A gateway URL the server reported: HTTPS (or loopback HTTP) with no credentials, else undefined. */
|
|
31
|
+
export function gatewayUrl(value) {
|
|
32
|
+
if (typeof value !== "string")
|
|
33
|
+
return undefined;
|
|
34
|
+
let url;
|
|
35
|
+
try {
|
|
36
|
+
url = new URL(value);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const loopback = /^(localhost|127\.\d+\.\d+\.\d+|\[::1\])$/.test(url.hostname);
|
|
42
|
+
if (url.username || url.password || url.search || url.hash)
|
|
43
|
+
return undefined;
|
|
44
|
+
if (url.protocol !== "https:" && !(loopback && url.protocol === "http:"))
|
|
45
|
+
return undefined;
|
|
46
|
+
return url.href;
|
|
47
|
+
}
|
|
30
48
|
function defaultOpenBrowser(ctx) {
|
|
31
49
|
return (url) => {
|
|
32
50
|
// Over SSH or without a display the URL and code are enough; nothing to open.
|
|
@@ -180,6 +198,8 @@ export async function setup(ctx, options) {
|
|
|
180
198
|
const both = enable.find((c) => disable.includes(c));
|
|
181
199
|
if (both)
|
|
182
200
|
throw new UserError(`${CLIENT_NAMES[both]} is in both --enable and --disable.`);
|
|
201
|
+
// The gateway can be on another host; only an HTTPS URL from this server is used.
|
|
202
|
+
const endpoint = gatewayUrl(account.mcpUrl) ?? `${state.server}/mcp`;
|
|
183
203
|
const workspace = await chooseWorkspace(ctx, state, account.workspaces, options.workspace);
|
|
184
204
|
state.workspace = { id: workspace.id, slug: workspace.slug, name: workspace.name };
|
|
185
205
|
saveState(state, stateEnv(ctx));
|
|
@@ -254,6 +274,10 @@ export async function setup(ctx, options) {
|
|
|
254
274
|
const want = wanted.has(target.client);
|
|
255
275
|
const link = state.agents[target.client];
|
|
256
276
|
if (want) {
|
|
277
|
+
if (live(target.client) && link && link.endpoint !== endpoint) {
|
|
278
|
+
link.endpoint = endpoint;
|
|
279
|
+
saveState(state, stateEnv(ctx));
|
|
280
|
+
}
|
|
257
281
|
if (!live(target.client)) {
|
|
258
282
|
if (link)
|
|
259
283
|
await removeRemote(link).catch(() => undefined);
|
|
@@ -263,6 +287,7 @@ export async function setup(ctx, options) {
|
|
|
263
287
|
id: created.id,
|
|
264
288
|
workspaceId: workspace.id,
|
|
265
289
|
server: state.server,
|
|
290
|
+
endpoint,
|
|
266
291
|
label: created.label,
|
|
267
292
|
token: created.token,
|
|
268
293
|
};
|
|
@@ -324,7 +349,7 @@ export async function setup(ctx, options) {
|
|
|
324
349
|
}
|
|
325
350
|
if ([...wanted].length) {
|
|
326
351
|
print(ctx);
|
|
327
|
-
print(ctx, `Choose which MCP servers they can use: ${state.server}
|
|
352
|
+
print(ctx, `Choose which MCP servers they can use: ${state.server}/${workspace.slug}/access`);
|
|
328
353
|
}
|
|
329
354
|
return manual.length ? 1 : 0;
|
|
330
355
|
}
|
|
@@ -338,7 +363,7 @@ export async function proxy(ctx, agent) {
|
|
|
338
363
|
throw new UserError(`${CLIENT_NAMES[client]} is not enabled on this machine. Run toolstile mcp setup.`);
|
|
339
364
|
await runProxy({
|
|
340
365
|
// The token goes only to the server that issued it, whatever the environment says.
|
|
341
|
-
endpoint: `${link.server}/mcp`,
|
|
366
|
+
endpoint: link.endpoint ?? `${link.server}/mcp`,
|
|
342
367
|
token: link.token,
|
|
343
368
|
input: ctx.stdin,
|
|
344
369
|
output: ctx.stdout,
|
package/dist/report.js
CHANGED
|
@@ -179,7 +179,7 @@ export function renderReport(r, home, s, redacted = false, columns = 80) {
|
|
|
179
179
|
lines.push(s.dim(wrap("Posting this? Run with --redact to hide your paths, or --card for an image with only the grade.", width)));
|
|
180
180
|
else
|
|
181
181
|
lines.push(s.dim("Share an image with only the grade: npx toolstile audit --card"));
|
|
182
|
-
lines.push(s.dim(wrap("Keep credentials off your machines and control each agent's tools: https://toolstile.com/?ref=cli", width)), "");
|
|
182
|
+
lines.push(s.dim(wrap("Keep credentials off your machines and control each agent's tools: https://www.toolstile.com/?ref=cli", width)), "");
|
|
183
183
|
return lines.join("\n");
|
|
184
184
|
}
|
|
185
185
|
/** JSON output for scripts. Paths are included unless redacted; secret values never are. */
|
package/dist/state.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { ClientId } from "./types.js";
|
|
2
|
-
export declare const DEFAULT_SERVER = "https://toolstile.com";
|
|
2
|
+
export declare const DEFAULT_SERVER = "https://app.toolstile.com";
|
|
3
|
+
/** Toolstile's gateway, on its own host since the app moved off the bare domain. */
|
|
4
|
+
export declare const DEFAULT_MCP_URL = "https://mcp.toolstile.com/mcp";
|
|
3
5
|
export interface AgentLink {
|
|
4
6
|
/** The agent's authorization ID in Toolstile, valid in one workspace. */
|
|
5
7
|
id: string;
|
|
6
8
|
workspaceId: string;
|
|
7
9
|
/** The Toolstile origin that issued the token; the proxy sends it nowhere else. */
|
|
8
10
|
server: string;
|
|
11
|
+
/** That server's gateway URL, as it reported it; `${server}/mcp` when absent. */
|
|
12
|
+
endpoint?: string;
|
|
9
13
|
label: string;
|
|
10
14
|
/** The agent's own gateway token; only `toolstile mcp proxy` reads it. */
|
|
11
15
|
token: string;
|
package/dist/state.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
export const DEFAULT_SERVER = "https://toolstile.com";
|
|
4
|
+
export const DEFAULT_SERVER = "https://app.toolstile.com";
|
|
5
|
+
/** Toolstile's gateway, on its own host since the app moved off the bare domain. */
|
|
6
|
+
export const DEFAULT_MCP_URL = "https://mcp.toolstile.com/mcp";
|
|
7
|
+
/** Where Toolstile served the app and gateway before; it now only redirects. */
|
|
8
|
+
const LEGACY_SERVER = "https://toolstile.com";
|
|
5
9
|
/** `TOOLSTILE_CONFIG_DIR`, else the platform's per-user config folder. */
|
|
6
10
|
export function stateDir({ home = homedir(), env = process.env, platform = process.platform } = {}) {
|
|
7
11
|
if (env.TOOLSTILE_CONFIG_DIR)
|
|
@@ -28,7 +32,7 @@ export function loadState(opts) {
|
|
|
28
32
|
}
|
|
29
33
|
try {
|
|
30
34
|
const data = JSON.parse(text);
|
|
31
|
-
return { ...fallback, ...data, agents: data.agents ?? {} };
|
|
35
|
+
return moveLegacyServer({ ...fallback, ...data, agents: data.agents ?? {} });
|
|
32
36
|
}
|
|
33
37
|
catch {
|
|
34
38
|
throw new Error(`${statePath(opts)} is not valid JSON. Delete it and run toolstile auth login again.`);
|
|
@@ -49,3 +53,16 @@ export function saveState(state, opts) {
|
|
|
49
53
|
rmSync(temp, { force: true });
|
|
50
54
|
}
|
|
51
55
|
}
|
|
56
|
+
/** Sign-ins and agents saved for the old bare-domain address keep working on the new hosts. */
|
|
57
|
+
function moveLegacyServer(state) {
|
|
58
|
+
if (state.server === LEGACY_SERVER)
|
|
59
|
+
state.server = DEFAULT_SERVER;
|
|
60
|
+
if (state.session?.server === LEGACY_SERVER)
|
|
61
|
+
state.session.server = DEFAULT_SERVER;
|
|
62
|
+
for (const link of Object.values(state.agents))
|
|
63
|
+
if (link?.server === LEGACY_SERVER) {
|
|
64
|
+
link.server = DEFAULT_SERVER;
|
|
65
|
+
link.endpoint ??= DEFAULT_MCP_URL;
|
|
66
|
+
}
|
|
67
|
+
return state;
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolstile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Audit the MCP server configs your AI agents use for plaintext secrets, unpinned packages, auto-approved tools and duplicated setups, and connect those agents to Toolstile without putting tokens in their configs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|