agent-dir 0.5.0 → 1.1.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 +40 -421
- package/SECURITY.md +39 -0
- package/assets/agent-dir-banner.svg +38 -0
- package/dist/bin/agent-dir.js +9 -7
- package/dist/src/auth.js +3 -1
- package/dist/src/codegraph.d.ts +1 -0
- package/dist/src/codegraph.js +12 -0
- package/dist/src/config.js +0 -2
- package/dist/src/logging.d.ts +1 -1
- package/dist/src/logging.js +9 -69
- package/dist/src/mcp.d.ts +2 -1
- package/dist/src/mcp.js +191 -653
- package/dist/src/server.d.ts +2 -2
- package/dist/src/server.js +45 -146
- package/dist/src/telemetry/privacy.js +2 -0
- package/dist/src/telemetry/types.d.ts +2 -0
- package/dist/src/tools/code.d.ts +0 -12
- package/dist/src/tools/code.js +9 -29
- package/dist/src/tools/diagnostics.js +3 -1
- package/dist/src/tools/files.d.ts +2 -21
- package/dist/src/tools/files.js +85 -70
- package/dist/src/tools/git.d.ts +0 -7
- package/dist/src/tools/git.js +0 -64
- package/dist/src/tools/metadata.d.ts +0 -1
- package/dist/src/tools/metadata.js +0 -22
- package/dist/src/tools/skills.d.ts +1 -0
- package/dist/src/tools/skills.js +65 -0
- package/dist/src/types.d.ts +1 -2
- package/docs/CONFIGURATION.md +154 -0
- package/docs/MCP.md +106 -0
- package/docs/RELEASING.md +204 -0
- package/docs/telemetry.md +109 -0
- package/package.json +4 -1
package/dist/src/tools/git.js
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { appendBoundedOutput, commandEnvironment, processTreeSpawnOptions, terminateProcessTree, } from "./commands.js";
|
|
3
|
-
function validatePaths(paths) {
|
|
4
|
-
if (!Array.isArray(paths) ||
|
|
5
|
-
paths.length === 0 ||
|
|
6
|
-
paths.some((path) => typeof path !== "string" || !path)) {
|
|
7
|
-
throw new Error("paths must contain at least one non-empty path.");
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
3
|
function runGit(root, args, timeoutMs = 30000) {
|
|
11
4
|
return new Promise((resolve, reject) => {
|
|
12
5
|
const child = spawn("git", args, {
|
|
@@ -47,63 +40,6 @@ function runGit(root, args, timeoutMs = 30000) {
|
|
|
47
40
|
});
|
|
48
41
|
});
|
|
49
42
|
}
|
|
50
|
-
export function gitStage(root, paths) {
|
|
51
|
-
validatePaths(paths);
|
|
52
|
-
return runGit(root, ["add", "--", ...paths]);
|
|
53
|
-
}
|
|
54
|
-
export function gitUnstage(root, paths) {
|
|
55
|
-
validatePaths(paths);
|
|
56
|
-
return runGit(root, ["restore", "--staged", "--", ...paths]);
|
|
57
|
-
}
|
|
58
|
-
export function gitCommit(root, message) {
|
|
59
|
-
if (!message.trim())
|
|
60
|
-
throw new Error("commit message must not be empty.");
|
|
61
|
-
return runGit(root, ["commit", "-m", message]);
|
|
62
|
-
}
|
|
63
|
-
export function gitPush(root, remote, branch) {
|
|
64
|
-
const args = ["push"];
|
|
65
|
-
if (remote) {
|
|
66
|
-
if (!/^[A-Za-z0-9._-]+$/.test(remote))
|
|
67
|
-
throw new Error("Invalid Git remote name.");
|
|
68
|
-
args.push(remote);
|
|
69
|
-
if (branch) {
|
|
70
|
-
if (!/^[A-Za-z0-9._/-]+$/.test(branch))
|
|
71
|
-
throw new Error("Invalid Git branch name.");
|
|
72
|
-
args.push(branch);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else if (branch) {
|
|
76
|
-
throw new Error("A Git remote is required when specifying a branch.");
|
|
77
|
-
}
|
|
78
|
-
return runGit(root, args);
|
|
79
|
-
}
|
|
80
43
|
export function gitStatus(root) {
|
|
81
44
|
return runGit(root, ["status", "--short", "--branch"]);
|
|
82
45
|
}
|
|
83
|
-
export function gitDiff(root, staged = false, pathspec) {
|
|
84
|
-
const args = ["diff", "--no-ext-diff", "--no-color"];
|
|
85
|
-
if (staged)
|
|
86
|
-
args.push("--cached");
|
|
87
|
-
if (pathspec)
|
|
88
|
-
args.push("--", pathspec);
|
|
89
|
-
return runGit(root, args);
|
|
90
|
-
}
|
|
91
|
-
export function gitRestore(root, paths, staged = false) {
|
|
92
|
-
validatePaths(paths);
|
|
93
|
-
return runGit(root, ["restore", ...(staged ? ["--staged"] : []), "--", ...paths]);
|
|
94
|
-
}
|
|
95
|
-
export function gitLog(root, limit = 20, pathspec) {
|
|
96
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 200)
|
|
97
|
-
throw new Error("limit must be an integer between 1 and 200.");
|
|
98
|
-
const args = [
|
|
99
|
-
"log",
|
|
100
|
-
"--no-color",
|
|
101
|
-
"--date=iso-strict",
|
|
102
|
-
"--format=%H%x09%ad%x09%an%x09%s",
|
|
103
|
-
"-n",
|
|
104
|
-
String(limit),
|
|
105
|
-
];
|
|
106
|
-
if (pathspec)
|
|
107
|
-
args.push("--", pathspec);
|
|
108
|
-
return runGit(root, args);
|
|
109
|
-
}
|
|
@@ -15,5 +15,4 @@ export interface ProjectOverview {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
export declare function fileInfo(root: string, relativePath: string): Promise<Record<string, unknown>>;
|
|
18
|
-
export declare function packageInfo(root: string): Promise<Record<string, unknown>>;
|
|
19
18
|
export declare function projectOverview(root: string): Promise<ProjectOverview>;
|
|
@@ -23,28 +23,6 @@ export async function fileInfo(root, relativePath) {
|
|
|
23
23
|
createdAt: stat.birthtime.toISOString(),
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
export async function packageInfo(root) {
|
|
27
|
-
const packagePath = path.join(await fs.realpath(root), "package.json");
|
|
28
|
-
try {
|
|
29
|
-
const packageJson = JSON.parse(await fs.readFile(packagePath, "utf8"));
|
|
30
|
-
return {
|
|
31
|
-
path: "package.json",
|
|
32
|
-
name: packageJson.name ?? null,
|
|
33
|
-
version: packageJson.version ?? null,
|
|
34
|
-
description: packageJson.description ?? null,
|
|
35
|
-
packageManager: packageJson.packageManager ?? null,
|
|
36
|
-
engines: packageJson.engines ?? null,
|
|
37
|
-
scripts: packageJson.scripts ?? {},
|
|
38
|
-
dependencies: packageJson.dependencies ?? {},
|
|
39
|
-
devDependencies: packageJson.devDependencies ?? {},
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
if (error.code === "ENOENT")
|
|
44
|
-
return { path: "package.json", exists: false };
|
|
45
|
-
throw new Error("package.json exists but is not valid JSON.");
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
26
|
export async function projectOverview(root) {
|
|
49
27
|
const entries = await listFiles(root);
|
|
50
28
|
const files = entries.filter((entry) => entry.type === "file");
|
package/dist/src/tools/skills.js
CHANGED
|
@@ -5,9 +5,15 @@ import { listFiles } from "./files.js";
|
|
|
5
5
|
const SKILL_ROOTS = ["skills", ".agents/skills", ".claude/skills", ".github/skills"];
|
|
6
6
|
const MAX_RESOURCES = 512;
|
|
7
7
|
const MAX_TOTAL_BYTES = 16 * 1024 * 1024;
|
|
8
|
+
const MAX_RESOURCE_RESPONSE_BYTES = 32_000;
|
|
8
9
|
const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9
10
|
const MAX_SKILL_NAME_LENGTH = 64;
|
|
10
11
|
const MAX_DESCRIPTION_LENGTH = 1024;
|
|
12
|
+
const DISCOVERY_CACHE_TTL_MS = 2_000;
|
|
13
|
+
const discoveryCache = new Map();
|
|
14
|
+
export function invalidateSkillDiscoveryCache(root) {
|
|
15
|
+
discoveryCache.delete(path.resolve(root));
|
|
16
|
+
}
|
|
11
17
|
export async function listSkills(root) {
|
|
12
18
|
return (await discoverSkills(root)).map((skill) => skill.entry);
|
|
13
19
|
}
|
|
@@ -22,8 +28,51 @@ export async function readSkillResource(root, uri) {
|
|
|
22
28
|
const filePath = skill.resources.get(uri);
|
|
23
29
|
if (!filePath)
|
|
24
30
|
continue;
|
|
31
|
+
const stats = await fs.stat(filePath);
|
|
32
|
+
if (stats.size > MAX_RESOURCE_RESPONSE_BYTES) {
|
|
33
|
+
const handle = await fs.open(filePath, "r");
|
|
34
|
+
let previewBytes;
|
|
35
|
+
try {
|
|
36
|
+
previewBytes = Buffer.alloc(MAX_RESOURCE_RESPONSE_BYTES);
|
|
37
|
+
const { bytesRead } = await handle.read(previewBytes, 0, previewBytes.length, 0);
|
|
38
|
+
previewBytes = previewBytes.subarray(0, bytesRead);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await handle.close();
|
|
42
|
+
}
|
|
43
|
+
if (previewBytes.includes(0)) {
|
|
44
|
+
return {
|
|
45
|
+
uri,
|
|
46
|
+
mimeType: "text/plain",
|
|
47
|
+
text: `Binary resource omitted: ${stats.size} bytes exceeds the ${MAX_RESOURCE_RESPONSE_BYTES}-byte response limit.`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const marker = "\n…[resource truncated at 32000 bytes]";
|
|
51
|
+
let preview = previewBytes
|
|
52
|
+
.subarray(0, MAX_RESOURCE_RESPONSE_BYTES - Buffer.byteLength(marker, "utf8"))
|
|
53
|
+
.toString("utf8");
|
|
54
|
+
while (preview.endsWith("\uFFFD"))
|
|
55
|
+
preview = preview.slice(0, -1);
|
|
56
|
+
return { uri, mimeType: mimeTypeFor(filePath), text: `${preview}${marker}` };
|
|
57
|
+
}
|
|
25
58
|
const bytes = await fs.readFile(filePath);
|
|
26
59
|
const text = bytes.toString("utf8");
|
|
60
|
+
if (bytes.length > MAX_RESOURCE_RESPONSE_BYTES) {
|
|
61
|
+
if (Buffer.from(text, "utf8").equals(bytes)) {
|
|
62
|
+
const marker = "\n…[resource truncated at 32000 bytes]";
|
|
63
|
+
let preview = bytes
|
|
64
|
+
.subarray(0, MAX_RESOURCE_RESPONSE_BYTES - Buffer.byteLength(marker, "utf8"))
|
|
65
|
+
.toString("utf8");
|
|
66
|
+
while (preview.endsWith("\uFFFD"))
|
|
67
|
+
preview = preview.slice(0, -1);
|
|
68
|
+
return { uri, mimeType: mimeTypeFor(filePath), text: `${preview}${marker}` };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
uri,
|
|
72
|
+
mimeType: "text/plain",
|
|
73
|
+
text: `Binary resource omitted: ${bytes.length} bytes exceeds the ${MAX_RESOURCE_RESPONSE_BYTES}-byte response limit.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
27
76
|
if (Buffer.from(text, "utf8").equals(bytes)) {
|
|
28
77
|
return { uri, mimeType: mimeTypeFor(filePath), text };
|
|
29
78
|
}
|
|
@@ -89,6 +138,22 @@ export async function skillResourceUrisForPath(root, relativePath) {
|
|
|
89
138
|
return [...matches].sort();
|
|
90
139
|
}
|
|
91
140
|
async function discoverSkills(root) {
|
|
141
|
+
const resolvedRoot = await fs.realpath(root);
|
|
142
|
+
const cached = discoveryCache.get(resolvedRoot);
|
|
143
|
+
if (cached && cached.expiresAt > Date.now())
|
|
144
|
+
return cached.promise;
|
|
145
|
+
const promise = discoverSkillsUncached(resolvedRoot);
|
|
146
|
+
discoveryCache.set(resolvedRoot, { expiresAt: Date.now() + DISCOVERY_CACHE_TTL_MS, promise });
|
|
147
|
+
try {
|
|
148
|
+
return await promise;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (discoveryCache.get(resolvedRoot)?.promise === promise)
|
|
152
|
+
discoveryCache.delete(resolvedRoot);
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function discoverSkillsUncached(root) {
|
|
92
157
|
const resolvedRoot = await fs.realpath(root);
|
|
93
158
|
const entries = await listFiles(root);
|
|
94
159
|
const skills = [];
|
package/dist/src/types.d.ts
CHANGED
|
@@ -24,7 +24,6 @@ export interface Profile {
|
|
|
24
24
|
npm?: NpmConfig;
|
|
25
25
|
commands?: string[];
|
|
26
26
|
blacklistedCommands?: string[];
|
|
27
|
-
git?: boolean;
|
|
28
27
|
}
|
|
29
28
|
export interface AgentConfig {
|
|
30
29
|
version: 1;
|
|
@@ -35,7 +34,6 @@ export interface CommandConfig {
|
|
|
35
34
|
npm?: NpmConfig;
|
|
36
35
|
commands?: string[];
|
|
37
36
|
blacklistedCommands?: string[];
|
|
38
|
-
git?: boolean;
|
|
39
37
|
}
|
|
40
38
|
export interface ServerOptions {
|
|
41
39
|
root: string;
|
|
@@ -43,6 +41,7 @@ export interface ServerOptions {
|
|
|
43
41
|
token: string;
|
|
44
42
|
commandConfig?: CommandConfig;
|
|
45
43
|
telemetry?: TelemetryConfig;
|
|
44
|
+
codeGraph?: import("./codegraph.js").CodeGraphIntegration;
|
|
46
45
|
}
|
|
47
46
|
export interface RequestLog {
|
|
48
47
|
method: string;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Configuration and tunnel setup
|
|
2
|
+
|
|
3
|
+
This guide covers first-run setup, saved profiles, authentication tokens, tunnels, and command permissions.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 24 or newer
|
|
8
|
+
- npm
|
|
9
|
+
- Wormhole CLI or `cloudflared` CLI for remote access
|
|
10
|
+
|
|
11
|
+
## First-run setup
|
|
12
|
+
|
|
13
|
+
When run interactively for the first time without an existing config, Agent Dir offers a setup wizard. It collects a profile name, project directory, and port, then optionally configures Cloudflare Tunnel and Wormhole. Cloudflare connector tokens are entered without being echoed. You can enable either provider, both, or neither; Cloudflare is the default when both are configured.
|
|
14
|
+
|
|
15
|
+
Run the wizard directly at any time:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
agent-dir setup
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The wizard saves the profile and exits without starting a tunnel. Non-interactive invocations and invocations with explicit launch options do not start the wizard.
|
|
22
|
+
|
|
23
|
+
To skip profile setup and start a temporary random Wormhole tunnel:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
agent-dir . --random --tunnel wormhole
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Profiles
|
|
30
|
+
|
|
31
|
+
Profiles store the project directory, port, tunnel settings, allowed npm scripts, allowed non-npm commands, and blocked command prefixes.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
agent-dir config add my-project \
|
|
35
|
+
--directory ~/src/my-project \
|
|
36
|
+
--tunnel wormhole \
|
|
37
|
+
--subdomain my-project-x7k4m2 \
|
|
38
|
+
--npm typecheck,lint,format,test,build \
|
|
39
|
+
--command grep,find,rg,git \
|
|
40
|
+
--blacklist "git commit,git push --force"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Start a saved profile by name:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
agent-dir my-project
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Manage profiles:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
agent-dir config list
|
|
53
|
+
agent-dir config show my-project
|
|
54
|
+
agent-dir config delete my-project
|
|
55
|
+
agent-dir config delete --all
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Deletion asks for confirmation by default. Use `--yes` for automation. The older `config remove <name>` command is an alias for `config delete <name>`.
|
|
59
|
+
|
|
60
|
+
Profile configuration is stored at `~/.config/agent-dir/config.json`, outside the project repository, with user-only permissions. CLI options can override saved values for a single run. `config show` redacts configured tunnel tokens.
|
|
61
|
+
|
|
62
|
+
### Example profile
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"directory": "~/src/my-project",
|
|
67
|
+
"port": 3002,
|
|
68
|
+
"tunnel": "cloudflared",
|
|
69
|
+
"defaultTunnel": "cloudflared",
|
|
70
|
+
"tunnels": {
|
|
71
|
+
"wormhole": { "subdomain": "my-project-x7k4m2" },
|
|
72
|
+
"cloudflared": {
|
|
73
|
+
"hostname": "mcp.example.com",
|
|
74
|
+
"token": "CLOUDFLARE_TUNNEL_TOKEN"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The Cloudflare connector token is separate from the Agent Dir MCP Bearer token. Avoid passing secrets as command-line arguments because they may appear in shell history or process listings; prefer the interactive prompt when possible.
|
|
81
|
+
|
|
82
|
+
## Tunnel setup
|
|
83
|
+
|
|
84
|
+
### Wormhole
|
|
85
|
+
|
|
86
|
+
Temporary random URL:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
agent-dir . --random --tunnel wormhole
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Custom subdomain:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
agent-dir . --tunnel wormhole --subdomain my-project-x7k4m2
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Choose a distinctive subdomain to reduce collisions. Wormhole limits how many subdomains a user can have registered or active. If registration fails with a subdomain limit error, stop unused Wormhole processes, release an existing subdomain, or use `--random`.
|
|
99
|
+
|
|
100
|
+
The tunnel is only transport; Agent Dir's local server still enforces its own authentication.
|
|
101
|
+
|
|
102
|
+
### Cloudflare Tunnel
|
|
103
|
+
|
|
104
|
+
Configure a Cloudflare Tunnel and DNS/ingress route for the hostname before launching Agent Dir:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
agent-dir . --tunnel cloudflared --hostname mcp.example.com
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For saved profiles, Cloudflare is selected by default when a `tunnels.cloudflared` entry exists. Use `-c` to explicitly select Cloudflare or `-w` to select Wormhole:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
agent-dir my-project -c
|
|
114
|
+
agent-dir my-project -w
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The `--hostname` value is the public URL Agent Dir displays; the named tunnel's DNS and ingress configuration must already exist. Agent Dir monitors `cloudflared tunnel run` and restarts the process if it disconnects. See the [Cloudflare Tunnel setup guide](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/).
|
|
118
|
+
|
|
119
|
+
## Authentication tokens
|
|
120
|
+
|
|
121
|
+
Agent Dir generates a random Bearer token when the server starts unless a profile token or `--token` is configured. Authentication cannot be disabled.
|
|
122
|
+
|
|
123
|
+
Retrieve or rotate a profile token:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
agent-dir config token my-project
|
|
127
|
+
agent-dir config token my-project --rotate
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The token command intentionally prints the secret. Do not share or commit its output.
|
|
131
|
+
|
|
132
|
+
Clients should send `Authorization: Bearer <token>`. Query-string tokens are supported for clients that cannot set headers, but are less secure because URLs may be recorded in logs or copied.
|
|
133
|
+
|
|
134
|
+
## Command permissions
|
|
135
|
+
|
|
136
|
+
npm scripts and general executables have separate allowlists.
|
|
137
|
+
|
|
138
|
+
Allow npm scripts:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
--npm typecheck,lint,format,test,build
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Allow executables:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
--command grep,find,rg,git
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Commands are launched with `shell: false`; executable and arguments are passed separately. Git uses the same allowlisted command interface as other executables; there are no dedicated Git MCP tools.
|
|
151
|
+
|
|
152
|
+
A configured `blacklistedCommands` entry overrides the allowlist and blocks matching command/argument prefixes. For example, allowing `git` while blacklisting `git commit` and `git push --force` still permits `git status`. Prefix entries are separated by commas.
|
|
153
|
+
|
|
154
|
+
Avoid allowing `sh`, `bash`, `zsh`, `cmd`, `node`, or `python` unless you intentionally want to grant much broader execution capability.
|
package/docs/MCP.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# MCP interface and security model
|
|
2
|
+
|
|
3
|
+
Agent Dir exposes a local project through an authenticated, stateless Model Context Protocol endpoint.
|
|
4
|
+
|
|
5
|
+
## Endpoint and authentication
|
|
6
|
+
|
|
7
|
+
- `POST /mcp` is the application MCP endpoint and requires authentication.
|
|
8
|
+
- `HEAD /__health` is reserved for local tunnel health checks and does not expose project contents.
|
|
9
|
+
- Legacy direct filesystem REST routes such as `/__tree` and direct `GET`/`PUT`/`DELETE` file paths are not supported.
|
|
10
|
+
|
|
11
|
+
Use the Bearer token in the standard header:
|
|
12
|
+
|
|
13
|
+
```http
|
|
14
|
+
Authorization: Bearer <token>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The server also accepts a query-string token for clients that cannot set an Authorization header. Prefer the header because query-string tokens can be exposed in URLs and logs.
|
|
18
|
+
|
|
19
|
+
## Stateless protocol behavior
|
|
20
|
+
|
|
21
|
+
The implementation supports MCP protocol version `2026-07-28`. Requests carry protocol version and client capabilities in `params._meta`; this path does not require an initialize handshake or `Mcp-Session-Id`.
|
|
22
|
+
|
|
23
|
+
Standard Streamable HTTP requests do not need implementation-specific `MCP-Protocol-Version`, `Mcp-Method`, or `Mcp-Name` headers. If supplied, these headers are validated against the JSON-RPC request and metadata.
|
|
24
|
+
|
|
25
|
+
Supported features include:
|
|
26
|
+
|
|
27
|
+
- `server/discover`
|
|
28
|
+
- cursor pagination for list-style methods
|
|
29
|
+
- `subscriptions/listen` for tool, prompt, and resource change events
|
|
30
|
+
- resource subscriptions and filesystem change notifications
|
|
31
|
+
- cache metadata
|
|
32
|
+
- structured tool results with `resultType` and `outputSchema`
|
|
33
|
+
- the stable `io.modelcontextprotocol/skills` extension
|
|
34
|
+
|
|
35
|
+
Modern `subscriptions/listen` replaces the legacy GET/SSE notification model. List-style methods use opaque cursors when more than 50 entries are available.
|
|
36
|
+
|
|
37
|
+
## Public tool set
|
|
38
|
+
|
|
39
|
+
The core public tool set is intentionally small:
|
|
40
|
+
|
|
41
|
+
| Tool | Purpose |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `project_context` | Compact project overview and active execution policy |
|
|
44
|
+
| `find_files` | Find paths by glob |
|
|
45
|
+
| `search_code` | Search source-like files with bounded output |
|
|
46
|
+
| `find_symbol` | Locate likely symbol definitions |
|
|
47
|
+
| `find_references` | Locate symbol references |
|
|
48
|
+
| `read_range` | Read a bounded line range from a UTF-8 file |
|
|
49
|
+
| `apply_changes` | Create, patch, or delete files |
|
|
50
|
+
| `file_info` | Inspect filesystem metadata |
|
|
51
|
+
| `validate` | Run lightweight diagnostics and explicitly requested allowed npm scripts |
|
|
52
|
+
| `run_npm_batch` | Run explicitly allowlisted npm scripts sequentially |
|
|
53
|
+
| `run_command_batch` | Run explicitly allowlisted executables sequentially without a shell |
|
|
54
|
+
| `codegraph_explore` | Optional structural code intelligence when CodeGraph is available |
|
|
55
|
+
|
|
56
|
+
`codegraph_explore` is only advertised when CodeGraph is installed and the current project has a readable `.codegraph/codegraph.db` index. Other CodeGraph tools are not forwarded.
|
|
57
|
+
|
|
58
|
+
Batch command operations execute sequentially and stop at the first failed command. Tool results include structured content and a serialized text representation; tools returning structured data advertise an output schema.
|
|
59
|
+
|
|
60
|
+
## Command execution policy
|
|
61
|
+
|
|
62
|
+
npm scripts and other executables use separate allowlists. An executable not in the configured allowlist is rejected, and configured `blacklistedCommands` prefixes override the allowlist. Commands are launched without a shell.
|
|
63
|
+
|
|
64
|
+
Git is run through `run_command_batch` when `git` is allowlisted. There are no dedicated Git MCP tools. This keeps the advertised tool set small while preserving explicit executable and argument restrictions. Avoid allowing shells or interpreters unless broader command execution is intentional.
|
|
65
|
+
|
|
66
|
+
## CodeGraph integration
|
|
67
|
+
|
|
68
|
+
CodeGraph is optional and installed separately:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install -g @colbymchenry/codegraph
|
|
72
|
+
codegraph init
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Agent Dir exposes `codegraph_explore` only when the executable is on `PATH` and the current project has a readable index. It starts CodeGraph on first use and reuses the child process for subsequent calls. The project root is fixed as the CodeGraph path; callers cannot forward arbitrary `projectPath` values. Agent Dir passes a limited environment to the child process and terminates it during shutdown.
|
|
76
|
+
|
|
77
|
+
Discovery distinguishes these states:
|
|
78
|
+
|
|
79
|
+
- `not_installed` — CodeGraph is unavailable on `PATH`.
|
|
80
|
+
- `not_indexed` — installed, but this project has no readable index.
|
|
81
|
+
- `available` — installed and indexed.
|
|
82
|
+
- `startup_failed` / `runtime_failed` — CodeGraph failed to start or later terminated; Agent Dir remains available.
|
|
83
|
+
|
|
84
|
+
When indexed, agents are instructed to use CodeGraph for non-trivial structural questions such as call/data flow, symbol relationships, and change impact. Exact-text searches and focused file edits use Agent Dir's native tools.
|
|
85
|
+
|
|
86
|
+
## Agent instructions and Skills
|
|
87
|
+
|
|
88
|
+
`server/discover` returns dynamically generated instructions. The Resources primitive exposes:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
agent-dir://instructions
|
|
92
|
+
agent-dir://capabilities
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The capabilities resource reports the Agent Dir version, registered tool names, execution policy, and preferred/avoid tool-selection patterns.
|
|
96
|
+
|
|
97
|
+
Agent Dir implements the stable MCP Skills extension (`io.modelcontextprotocol/skills`) and discovers project-local `SKILL.md` files under:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
skills/
|
|
101
|
+
.agents/skills/
|
|
102
|
+
.claude/skills/
|
|
103
|
+
.github/skills/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Skills are exposed through `skills/list`, `skills/get`, `resources/list`, and `resources/read`. Skill manifests are bounded to 512 resources and 16 MiB. Binary supporting files are returned as MCP blobs; `resources/directory/read` lists direct children of a skill resource directory.
|