create-academic-research 0.1.13 → 0.1.15
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 +71 -11
- package/dist/bin/academic-research.js +0 -0
- package/dist/bin/create-academic-research.js +0 -0
- package/dist/src/capabilities.d.ts +132 -3
- package/dist/src/capabilities.js +993 -48
- package/dist/src/cli.js +448 -33
- package/dist/src/mcp-env.d.ts +4 -0
- package/dist/src/mcp-env.js +2 -2
- package/dist/src/mcp-probe.d.ts +3 -2
- package/dist/src/mcp-probe.js +87 -30
- package/dist/src/project.d.ts +18 -0
- package/dist/src/project.js +654 -22
- package/dist/src/stack.d.ts +38 -0
- package/dist/src/stack.js +260 -14
- package/package.json +2 -2
- package/template/README.md +37 -4
- package/template/_gitignore +1 -0
- package/template/docs/agent/mcp-client-setup.md +43 -3
- package/template/docs/agent/mcp-setup.md +60 -0
- package/template/docs/getting-started.md +17 -5
- package/template/package.json +7 -1
package/dist/src/mcp-env.d.ts
CHANGED
|
@@ -7,10 +7,14 @@ export interface McpEnvironmentEntry {
|
|
|
7
7
|
export declare function listMcpEnvironmentEntries(servers: string[], options?: {
|
|
8
8
|
requiredOnly?: boolean;
|
|
9
9
|
recommendedOnly?: boolean;
|
|
10
|
+
mode?: string;
|
|
11
|
+
modes?: Record<string, string | undefined>;
|
|
10
12
|
}): McpEnvironmentEntry[];
|
|
11
13
|
export declare function formatMcpDotenv(servers: string[], options?: {
|
|
12
14
|
requiredOnly?: boolean;
|
|
13
15
|
recommendedOnly?: boolean;
|
|
16
|
+
mode?: string;
|
|
17
|
+
modes?: Record<string, string | undefined>;
|
|
14
18
|
}): string;
|
|
15
19
|
export declare function readMcpEnvironmentFile(path: string): Promise<Record<string, string>>;
|
|
16
20
|
export declare function mergeMcpEnvironment(baseEnv?: NodeJS.ProcessEnv, fileEnv?: Record<string, string>): NodeJS.ProcessEnv;
|
package/dist/src/mcp-env.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { AGENT_STACK } from "./stack.js";
|
|
2
|
+
import { AGENT_STACK, resolveMcpServer } from "./stack.js";
|
|
3
3
|
export function listMcpEnvironmentEntries(servers, options = {}) {
|
|
4
4
|
assertKnownMcpServers(servers);
|
|
5
5
|
const entries = [];
|
|
6
6
|
for (const serverName of servers) {
|
|
7
|
-
const server =
|
|
7
|
+
const server = resolveMcpServer(serverName, options.mode ?? options.modes?.[serverName]);
|
|
8
8
|
if (!options.recommendedOnly) {
|
|
9
9
|
for (const envName of server.required_env) {
|
|
10
10
|
entries.push({ server: serverName, kind: "required", name: envName, value: "" });
|
package/dist/src/mcp-probe.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ResolvedMcpServer } from "./stack.js";
|
|
2
|
+
export type McpProbeStatus = "ok" | "manual" | "remote-configured" | "missing-remote-url" | "missing-env" | "runtime-missing" | "startup-failed" | "protocol-error" | "timeout";
|
|
2
3
|
export interface McpProbeServerResult {
|
|
3
4
|
server: string;
|
|
4
5
|
status: McpProbeStatus;
|
|
@@ -8,4 +9,4 @@ export interface McpProbeResult {
|
|
|
8
9
|
ok: boolean;
|
|
9
10
|
results: McpProbeServerResult[];
|
|
10
11
|
}
|
|
11
|
-
export declare function probeMcpServerList(root: string, servers: string[], env: NodeJS.ProcessEnv, timeoutMs: number, clientVersion?: string): Promise<McpProbeResult>;
|
|
12
|
+
export declare function probeMcpServerList(root: string, servers: string[], env: NodeJS.ProcessEnv, timeoutMs: number, clientVersion?: string, modes?: Record<string, string>, resolvedServers?: Record<string, ResolvedMcpServer>): Promise<McpProbeResult>;
|
package/dist/src/mcp-probe.js
CHANGED
|
@@ -1,28 +1,46 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { delimiter, join } from "node:path";
|
|
4
|
-
import { AGENT_STACK } from "./stack.js";
|
|
5
|
-
export async function probeMcpServerList(root, servers, env, timeoutMs, clientVersion = "unknown") {
|
|
3
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
4
|
+
import { AGENT_STACK, resolveMcpServer } from "./stack.js";
|
|
5
|
+
export async function probeMcpServerList(root, servers, env, timeoutMs, clientVersion = "unknown", modes = {}, resolvedServers = {}) {
|
|
6
6
|
assertKnownMcpServers(servers);
|
|
7
7
|
const results = [];
|
|
8
8
|
for (const name of servers) {
|
|
9
|
-
const server =
|
|
9
|
+
const server = resolvedServers[name] ?? resolveMcpServer(name, modes[name]);
|
|
10
10
|
const missingRequired = server.required_env.filter((envName) => !envHasValue(env, envName));
|
|
11
11
|
if (missingRequired.length > 0) {
|
|
12
12
|
results.push({ server: name, status: "missing-env", detail: missingRequired.join(",") });
|
|
13
13
|
continue;
|
|
14
14
|
}
|
|
15
|
+
if (server.connection_mode === "remote-custom" && !server.remote_configured) {
|
|
16
|
+
results.push({ server: name, status: "missing-remote-url", detail: "custom remote endpoint not configured" });
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (server.connection_mode === "remote-curated" || server.connection_mode === "remote-custom") {
|
|
20
|
+
results.push({ server: name, status: "remote-configured", detail: remoteProbeDetail(server) });
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
15
23
|
if (!server.command) {
|
|
16
|
-
results.push({ server: name, status: "manual", detail: server.local_service || "manual setup only" });
|
|
24
|
+
results.push({ server: name, status: "manual", detail: server.hosted_url || server.local_service || "manual setup only" });
|
|
17
25
|
continue;
|
|
18
26
|
}
|
|
19
|
-
|
|
27
|
+
const command = resolveCommand(root, server.command);
|
|
28
|
+
if (!commandExists(command, env)) {
|
|
20
29
|
results.push({ server: name, status: "runtime-missing", detail: server.command });
|
|
21
30
|
continue;
|
|
22
31
|
}
|
|
23
|
-
results.push(await probeMcpServerProcess(root, name,
|
|
32
|
+
results.push(await probeMcpServerProcess(root, name, command, server.args, { ...server.env, ...env }, timeoutMs, clientVersion));
|
|
24
33
|
}
|
|
25
|
-
return { ok: results.every((result) => result.status === "ok"), results };
|
|
34
|
+
return { ok: results.every((result) => result.status === "ok" || result.status === "remote-configured"), results };
|
|
35
|
+
}
|
|
36
|
+
function remoteProbeDetail(server) {
|
|
37
|
+
const urlEnv = server.connection_mode === "remote-custom" ? server.remote_url_env : undefined;
|
|
38
|
+
const source = urlEnv
|
|
39
|
+
? `custom remote endpoint from ${urlEnv}`
|
|
40
|
+
: server.connection_mode === "remote-custom"
|
|
41
|
+
? "custom remote endpoint configured"
|
|
42
|
+
: "remote endpoint configured";
|
|
43
|
+
return `${source}; remote probe does not perform a stdio handshake`;
|
|
26
44
|
}
|
|
27
45
|
function assertKnownMcpServers(servers) {
|
|
28
46
|
const unknown = servers.filter((server) => !AGENT_STACK.mcp_servers[server]);
|
|
@@ -52,6 +70,11 @@ function commandExists(command, env = process.env) {
|
|
|
52
70
|
}
|
|
53
71
|
return false;
|
|
54
72
|
}
|
|
73
|
+
function resolveCommand(root, command) {
|
|
74
|
+
if (!command.includes("/") && !command.includes("\\"))
|
|
75
|
+
return command;
|
|
76
|
+
return isAbsolute(command) ? command : join(root, command);
|
|
77
|
+
}
|
|
55
78
|
async function probeMcpServerProcess(root, server, command, args, env, timeoutMs, clientVersion) {
|
|
56
79
|
return new Promise((resolve) => {
|
|
57
80
|
let settled = false;
|
|
@@ -96,7 +119,7 @@ async function probeMcpServerProcess(root, server, command, args, env, timeoutMs
|
|
|
96
119
|
}
|
|
97
120
|
});
|
|
98
121
|
try {
|
|
99
|
-
child.stdin.write(
|
|
122
|
+
child.stdin.write(encodeMcpLineMessage({
|
|
100
123
|
jsonrpc: "2.0",
|
|
101
124
|
id: 1,
|
|
102
125
|
method: "initialize",
|
|
@@ -116,12 +139,12 @@ async function probeMcpServerProcess(root, server, command, args, env, timeoutMs
|
|
|
116
139
|
finish("protocol-error", formatJsonRpcError(message.error));
|
|
117
140
|
return;
|
|
118
141
|
}
|
|
119
|
-
child.stdin.write(
|
|
142
|
+
child.stdin.write(encodeMcpLineMessage({
|
|
120
143
|
jsonrpc: "2.0",
|
|
121
144
|
method: "notifications/initialized",
|
|
122
145
|
params: {}
|
|
123
146
|
}));
|
|
124
|
-
child.stdin.write(
|
|
147
|
+
child.stdin.write(encodeMcpLineMessage({
|
|
125
148
|
jsonrpc: "2.0",
|
|
126
149
|
id: 2,
|
|
127
150
|
method: "tools/list",
|
|
@@ -143,31 +166,65 @@ async function probeMcpServerProcess(root, server, command, args, env, timeoutMs
|
|
|
143
166
|
function drainMcpMessages() {
|
|
144
167
|
const messages = [];
|
|
145
168
|
while (true) {
|
|
146
|
-
|
|
147
|
-
if (
|
|
169
|
+
stdout = trimLeadingMcpWhitespace(stdout);
|
|
170
|
+
if (stdout.length === 0)
|
|
148
171
|
return messages;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
172
|
+
if (startsWithContentLength(stdout)) {
|
|
173
|
+
const framed = drainContentLengthMessage(stdout);
|
|
174
|
+
if (!framed)
|
|
175
|
+
return messages;
|
|
176
|
+
stdout = framed.remaining;
|
|
177
|
+
messages.push(parseMcpMessage(framed.body));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const newline = stdout.indexOf("\n");
|
|
181
|
+
if (newline === -1)
|
|
157
182
|
return messages;
|
|
158
|
-
const
|
|
159
|
-
stdout = stdout.slice(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
messages.push(parsed);
|
|
183
|
+
const line = stdout.slice(0, newline).toString("utf8").trim();
|
|
184
|
+
stdout = stdout.slice(newline + 1);
|
|
185
|
+
if (!line)
|
|
186
|
+
continue;
|
|
187
|
+
messages.push(parseMcpMessage(line));
|
|
164
188
|
}
|
|
165
189
|
}
|
|
166
190
|
});
|
|
167
191
|
}
|
|
168
|
-
function
|
|
169
|
-
|
|
170
|
-
|
|
192
|
+
function encodeMcpLineMessage(message) {
|
|
193
|
+
return `${JSON.stringify(message)}\n`;
|
|
194
|
+
}
|
|
195
|
+
function trimLeadingMcpWhitespace(buffer) {
|
|
196
|
+
let offset = 0;
|
|
197
|
+
while (offset < buffer.length && (buffer[offset] === 0x0a || buffer[offset] === 0x0d)) {
|
|
198
|
+
offset += 1;
|
|
199
|
+
}
|
|
200
|
+
return offset === 0 ? buffer : buffer.slice(offset);
|
|
201
|
+
}
|
|
202
|
+
function startsWithContentLength(buffer) {
|
|
203
|
+
return buffer.slice(0, "Content-Length:".length).toString("utf8").toLowerCase() === "content-length:";
|
|
204
|
+
}
|
|
205
|
+
function drainContentLengthMessage(buffer) {
|
|
206
|
+
const separator = buffer.indexOf("\r\n\r\n");
|
|
207
|
+
if (separator === -1)
|
|
208
|
+
return undefined;
|
|
209
|
+
const header = buffer.slice(0, separator).toString("utf8");
|
|
210
|
+
const match = /Content-Length:\s*(\d+)/i.exec(header);
|
|
211
|
+
if (!match)
|
|
212
|
+
throw new Error("missing Content-Length header");
|
|
213
|
+
const length = Number(match[1]);
|
|
214
|
+
const bodyStart = separator + 4;
|
|
215
|
+
const bodyEnd = bodyStart + length;
|
|
216
|
+
if (buffer.length < bodyEnd)
|
|
217
|
+
return undefined;
|
|
218
|
+
return {
|
|
219
|
+
body: buffer.slice(bodyStart, bodyEnd).toString("utf8"),
|
|
220
|
+
remaining: buffer.slice(bodyEnd)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function parseMcpMessage(raw) {
|
|
224
|
+
const parsed = JSON.parse(raw);
|
|
225
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
226
|
+
throw new Error("MCP response is not an object");
|
|
227
|
+
return parsed;
|
|
171
228
|
}
|
|
172
229
|
function formatJsonRpcError(error) {
|
|
173
230
|
if (typeof error === "object" && error !== null && "message" in error) {
|
package/dist/src/project.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export interface RenameProjectOptions {
|
|
|
13
13
|
slug?: string;
|
|
14
14
|
packageName?: string;
|
|
15
15
|
}
|
|
16
|
+
export interface InitProjectOptions extends CreateProjectOptions {
|
|
17
|
+
}
|
|
18
|
+
export interface UpdateProjectOptions {
|
|
19
|
+
apply?: boolean;
|
|
20
|
+
}
|
|
16
21
|
export interface ProjectResult {
|
|
17
22
|
root: string;
|
|
18
23
|
title: string;
|
|
@@ -22,7 +27,20 @@ export interface ProjectResult {
|
|
|
22
27
|
export interface DoctorResult {
|
|
23
28
|
ok: boolean;
|
|
24
29
|
errors: string[];
|
|
30
|
+
warnings: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface ProjectFileChange {
|
|
33
|
+
path: string;
|
|
34
|
+
action: "create" | "update" | "skip";
|
|
35
|
+
reason?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface UpdateProjectResult {
|
|
38
|
+
root: string;
|
|
39
|
+
applied: boolean;
|
|
40
|
+
changes: ProjectFileChange[];
|
|
25
41
|
}
|
|
26
42
|
export declare function createProject(options: CreateProjectOptions): Promise<ProjectResult>;
|
|
43
|
+
export declare function initProject(options: InitProjectOptions): Promise<ProjectResult>;
|
|
27
44
|
export declare function renameProject(root: string, options: RenameProjectOptions): Promise<ProjectResult>;
|
|
45
|
+
export declare function updateProject(root: string, options?: UpdateProjectOptions): Promise<UpdateProjectResult>;
|
|
28
46
|
export declare function doctorProject(root: string): Promise<DoctorResult>;
|