@intelligentelectron/pdf-analyzer 0.0.3
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/LICENSE +190 -0
- package/README.md +220 -0
- package/dist/cli/commands.d.ts +19 -0
- package/dist/cli/commands.js +147 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/shell.d.ts +12 -0
- package/dist/cli/shell.js +65 -0
- package/dist/cli/shell.js.map +1 -0
- package/dist/cli/updater.d.ts +38 -0
- package/dist/cli/updater.js +301 -0
- package/dist/cli/updater.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.js +154 -0
- package/dist/server.js.map +1 -0
- package/dist/service.d.ts +37 -0
- package/dist/service.js +240 -0
- package/dist/service.js.map +1 -0
- package/dist/service.test.d.ts +1 -0
- package/dist/service.test.js +108 -0
- package/dist/service.test.js.map +1 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.js +30 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +32 -0
- package/dist/version.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-updater for pdf-analyzer server.
|
|
3
|
+
*
|
|
4
|
+
* Checks GitHub Releases for newer versions and self-updates on startup.
|
|
5
|
+
* Can be disabled via PDF_MCP_NO_UPDATE=1 environment variable.
|
|
6
|
+
*/
|
|
7
|
+
/** Result of an update check. */
|
|
8
|
+
export interface UpdateCheckResult {
|
|
9
|
+
updateAvailable: boolean;
|
|
10
|
+
currentVersion: string;
|
|
11
|
+
latestVersion: string | null;
|
|
12
|
+
downloadUrl: string | null;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Result of an update operation. */
|
|
16
|
+
export interface UpdateResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
previousVersion: string;
|
|
19
|
+
newVersion: string | null;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if an update is available.
|
|
24
|
+
*/
|
|
25
|
+
export declare const checkForUpdate: () => Promise<UpdateCheckResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Perform the update by downloading and replacing the current binary.
|
|
28
|
+
*/
|
|
29
|
+
export declare const performUpdate: (downloadUrl: string, newVersion: string) => Promise<UpdateResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Re-execute the current process with the same arguments.
|
|
32
|
+
*/
|
|
33
|
+
export declare const reexec: () => never;
|
|
34
|
+
/**
|
|
35
|
+
* Check for updates and apply if available.
|
|
36
|
+
* This is the main entry point for auto-updates on startup.
|
|
37
|
+
*/
|
|
38
|
+
export declare const autoUpdate: () => Promise<boolean>;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-updater for pdf-analyzer server.
|
|
3
|
+
*
|
|
4
|
+
* Checks GitHub Releases for newer versions and self-updates on startup.
|
|
5
|
+
* Can be disabled via PDF_MCP_NO_UPDATE=1 environment variable.
|
|
6
|
+
*/
|
|
7
|
+
import { createWriteStream, chmodSync, renameSync, unlinkSync, existsSync, readdirSync, } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join, dirname, basename } from "node:path";
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { VERSION, GITHUB_REPO, BINARY_NAME } from "../version.js";
|
|
12
|
+
/**
|
|
13
|
+
* Get the platform-specific binary name.
|
|
14
|
+
*/
|
|
15
|
+
const getPlatformBinaryName = () => {
|
|
16
|
+
const platform = process.platform;
|
|
17
|
+
const arch = process.arch;
|
|
18
|
+
if (platform === "darwin") {
|
|
19
|
+
return arch === "arm64" ? `${BINARY_NAME}-darwin-arm64` : `${BINARY_NAME}-darwin-x64`;
|
|
20
|
+
}
|
|
21
|
+
else if (platform === "linux") {
|
|
22
|
+
return arch === "arm64" ? `${BINARY_NAME}-linux-arm64` : `${BINARY_NAME}-linux-x64`;
|
|
23
|
+
}
|
|
24
|
+
else if (platform === "win32") {
|
|
25
|
+
return `${BINARY_NAME}-windows-x64.exe`;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Unsupported platform: ${platform}-${arch}`);
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Parse a version string into comparable parts.
|
|
31
|
+
*/
|
|
32
|
+
const parseVersion = (version) => {
|
|
33
|
+
const cleaned = version.replace(/^v/, "");
|
|
34
|
+
return cleaned.split(".").map((part) => parseInt(part, 10) || 0);
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Compare two version strings. Returns:
|
|
38
|
+
* - Negative if a < b
|
|
39
|
+
* - Zero if a === b
|
|
40
|
+
* - Positive if a > b
|
|
41
|
+
*/
|
|
42
|
+
const compareVersions = (a, b) => {
|
|
43
|
+
const partsA = parseVersion(a);
|
|
44
|
+
const partsB = parseVersion(b);
|
|
45
|
+
const maxLen = Math.max(partsA.length, partsB.length);
|
|
46
|
+
for (let i = 0; i < maxLen; i++) {
|
|
47
|
+
const numA = partsA[i] || 0;
|
|
48
|
+
const numB = partsB[i] || 0;
|
|
49
|
+
if (numA !== numB) {
|
|
50
|
+
return numA - numB;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Fetch the latest release from GitHub.
|
|
57
|
+
*/
|
|
58
|
+
const fetchLatestRelease = async () => {
|
|
59
|
+
const url = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
|
|
60
|
+
const response = await fetch(url, {
|
|
61
|
+
headers: {
|
|
62
|
+
Accept: "application/vnd.github.v3+json",
|
|
63
|
+
"User-Agent": `${BINARY_NAME}/${VERSION}`,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
if (response.status === 404) {
|
|
68
|
+
throw new Error("No releases found");
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
71
|
+
}
|
|
72
|
+
return response.json();
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Check if an update is available.
|
|
76
|
+
*/
|
|
77
|
+
export const checkForUpdate = async () => {
|
|
78
|
+
try {
|
|
79
|
+
const release = await fetchLatestRelease();
|
|
80
|
+
const latestVersion = release.tag_name.replace(/^v/, "");
|
|
81
|
+
const updateAvailable = compareVersions(latestVersion, VERSION) > 0;
|
|
82
|
+
let downloadUrl = null;
|
|
83
|
+
if (updateAvailable) {
|
|
84
|
+
const binaryName = getPlatformBinaryName();
|
|
85
|
+
const asset = release.assets.find((a) => a.name === binaryName);
|
|
86
|
+
if (asset) {
|
|
87
|
+
downloadUrl = asset.browser_download_url;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
updateAvailable,
|
|
92
|
+
currentVersion: VERSION,
|
|
93
|
+
latestVersion,
|
|
94
|
+
downloadUrl,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
updateAvailable: false,
|
|
100
|
+
currentVersion: VERSION,
|
|
101
|
+
latestVersion: null,
|
|
102
|
+
downloadUrl: null,
|
|
103
|
+
error: error instanceof Error ? error.message : String(error),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Download a file from a URL to a local path.
|
|
109
|
+
*/
|
|
110
|
+
const downloadFile = async (url, destPath) => {
|
|
111
|
+
const response = await fetch(url, {
|
|
112
|
+
headers: {
|
|
113
|
+
"User-Agent": `${BINARY_NAME}/${VERSION}`,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
118
|
+
}
|
|
119
|
+
const fileStream = createWriteStream(destPath);
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
if (!response.body) {
|
|
122
|
+
reject(new Error("No response body"));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const reader = response.body.getReader();
|
|
126
|
+
const pump = async () => {
|
|
127
|
+
const { done, value } = await reader.read();
|
|
128
|
+
if (done) {
|
|
129
|
+
fileStream.end();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
fileStream.write(Buffer.from(value));
|
|
133
|
+
return pump();
|
|
134
|
+
};
|
|
135
|
+
fileStream.on("finish", resolve);
|
|
136
|
+
fileStream.on("error", reject);
|
|
137
|
+
pump().catch(reject);
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Get the path to the current executable.
|
|
142
|
+
*/
|
|
143
|
+
const getCurrentExecutablePath = () => {
|
|
144
|
+
// For Bun-compiled binaries, process.execPath points to the binary itself
|
|
145
|
+
// For Node.js, process.argv[1] is the script path
|
|
146
|
+
if (process.execPath.includes("node") || process.execPath.includes("bun")) {
|
|
147
|
+
// Running via node/bun interpreter - use argv[1]
|
|
148
|
+
return process.argv[1];
|
|
149
|
+
}
|
|
150
|
+
// Compiled binary - use execPath
|
|
151
|
+
return process.execPath;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Generate a unique backup path with timestamp to avoid conflicts with locked files.
|
|
155
|
+
* On Windows, previous backup files may still be locked by the old process.
|
|
156
|
+
*/
|
|
157
|
+
const getBackupPath = (currentPath) => {
|
|
158
|
+
return `${currentPath}.backup.${Date.now()}`;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Clean up old backup files from previous updates (best effort).
|
|
162
|
+
* On Windows, backup files may remain if the old process was still running.
|
|
163
|
+
* This is non-fatal - if files are locked, they'll be cleaned up next time.
|
|
164
|
+
*/
|
|
165
|
+
const cleanupOldBackups = (currentPath) => {
|
|
166
|
+
const dir = dirname(currentPath);
|
|
167
|
+
const base = basename(currentPath);
|
|
168
|
+
try {
|
|
169
|
+
const files = readdirSync(dir);
|
|
170
|
+
for (const file of files) {
|
|
171
|
+
if (file.startsWith(`${base}.backup.`)) {
|
|
172
|
+
try {
|
|
173
|
+
unlinkSync(join(dir, file));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Ignore - file may still be locked
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Ignore directory read errors
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Perform the update by downloading and replacing the current binary.
|
|
187
|
+
*/
|
|
188
|
+
export const performUpdate = async (downloadUrl, newVersion) => {
|
|
189
|
+
const currentPath = getCurrentExecutablePath();
|
|
190
|
+
const tempPath = join(tmpdir(), `${BINARY_NAME}-update-${Date.now()}`);
|
|
191
|
+
// Use timestamped backup to avoid conflicts with locked files from previous updates
|
|
192
|
+
const backupPath = getBackupPath(currentPath);
|
|
193
|
+
// Clean up old backups from previous updates (best effort, non-fatal)
|
|
194
|
+
cleanupOldBackups(currentPath);
|
|
195
|
+
try {
|
|
196
|
+
// Download new binary to temp location
|
|
197
|
+
await downloadFile(downloadUrl, tempPath);
|
|
198
|
+
// Make executable
|
|
199
|
+
if (process.platform !== "win32") {
|
|
200
|
+
chmodSync(tempPath, 0o755);
|
|
201
|
+
}
|
|
202
|
+
// Backup current binary
|
|
203
|
+
if (existsSync(currentPath)) {
|
|
204
|
+
renameSync(currentPath, backupPath);
|
|
205
|
+
}
|
|
206
|
+
// Move new binary into place
|
|
207
|
+
renameSync(tempPath, currentPath);
|
|
208
|
+
// Remove backup (non-fatal on Windows due to file locking)
|
|
209
|
+
if (existsSync(backupPath)) {
|
|
210
|
+
try {
|
|
211
|
+
unlinkSync(backupPath);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// On Windows, the old executable may still be locked.
|
|
215
|
+
// This is fine - it will be cleaned up on next update.
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
success: true,
|
|
220
|
+
previousVersion: VERSION,
|
|
221
|
+
newVersion,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
// Attempt to restore backup
|
|
226
|
+
if (existsSync(backupPath) && !existsSync(currentPath)) {
|
|
227
|
+
try {
|
|
228
|
+
renameSync(backupPath, currentPath);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Ignore restore errors
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Clean up temp file
|
|
235
|
+
if (existsSync(tempPath)) {
|
|
236
|
+
try {
|
|
237
|
+
unlinkSync(tempPath);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Ignore cleanup errors
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
previousVersion: VERSION,
|
|
246
|
+
newVersion: null,
|
|
247
|
+
error: error instanceof Error ? error.message : String(error),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Re-execute the current process with the same arguments.
|
|
253
|
+
*/
|
|
254
|
+
export const reexec = () => {
|
|
255
|
+
const execPath = getCurrentExecutablePath();
|
|
256
|
+
const args = process.argv.slice(2);
|
|
257
|
+
// Spawn the new process
|
|
258
|
+
const child = spawn(execPath, args, {
|
|
259
|
+
stdio: "inherit",
|
|
260
|
+
detached: false,
|
|
261
|
+
});
|
|
262
|
+
// Exit this process when child exits
|
|
263
|
+
child.on("exit", (code) => {
|
|
264
|
+
process.exit(code ?? 0);
|
|
265
|
+
});
|
|
266
|
+
child.on("error", (err) => {
|
|
267
|
+
console.error("Failed to restart:", err.message);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
});
|
|
270
|
+
// This line is reached but we've set up handlers to exit
|
|
271
|
+
// TypeScript needs the never return type satisfied
|
|
272
|
+
throw new Error("Process should have been replaced");
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Check for updates and apply if available.
|
|
276
|
+
* This is the main entry point for auto-updates on startup.
|
|
277
|
+
*/
|
|
278
|
+
export const autoUpdate = async () => {
|
|
279
|
+
// Check if updates are disabled
|
|
280
|
+
if (process.env.PDF_MCP_NO_UPDATE === "1") {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
const check = await checkForUpdate();
|
|
284
|
+
if (check.error) {
|
|
285
|
+
// Silently continue if update check fails
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
if (!check.updateAvailable || !check.downloadUrl || !check.latestVersion) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
// Log update to stderr (MCP uses stdio, so stdout is reserved)
|
|
292
|
+
console.error(`[pdf-analyzer] Updating from ${VERSION} to ${check.latestVersion}...`);
|
|
293
|
+
const result = await performUpdate(check.downloadUrl, check.latestVersion);
|
|
294
|
+
if (!result.success) {
|
|
295
|
+
console.error(`[pdf-analyzer] Update failed: ${result.error}`);
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
console.error(`[pdf-analyzer] Update complete. Restarting...`);
|
|
299
|
+
return true;
|
|
300
|
+
};
|
|
301
|
+
//# sourceMappingURL=updater.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updater.js","sourceRoot":"","sources":["../../src/cli/updater.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PDF Analyzer MCP Server Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Run with: npx tsx src/index.ts
|
|
6
|
+
* Or after build: node dist/index.js
|
|
7
|
+
*
|
|
8
|
+
* CLI flags:
|
|
9
|
+
* --version, -v Print version and exit
|
|
10
|
+
* --update Check for updates and apply if available
|
|
11
|
+
* --uninstall Remove pdf-analyzer from the system
|
|
12
|
+
* --no-update Skip auto-update check on startup
|
|
13
|
+
* --help, -h Show help
|
|
14
|
+
*
|
|
15
|
+
* Environment variables:
|
|
16
|
+
* GEMINI_API_KEY Required. Your Gemini API key.
|
|
17
|
+
* PDF_MCP_NO_UPDATE=1 Disable auto-updates
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PDF Analyzer MCP Server Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Run with: npx tsx src/index.ts
|
|
6
|
+
* Or after build: node dist/index.js
|
|
7
|
+
*
|
|
8
|
+
* CLI flags:
|
|
9
|
+
* --version, -v Print version and exit
|
|
10
|
+
* --update Check for updates and apply if available
|
|
11
|
+
* --uninstall Remove pdf-analyzer from the system
|
|
12
|
+
* --no-update Skip auto-update check on startup
|
|
13
|
+
* --help, -h Show help
|
|
14
|
+
*
|
|
15
|
+
* Environment variables:
|
|
16
|
+
* GEMINI_API_KEY Required. Your Gemini API key.
|
|
17
|
+
* PDF_MCP_NO_UPDATE=1 Disable auto-updates
|
|
18
|
+
*/
|
|
19
|
+
import { autoUpdate, reexec } from "./cli/updater.js";
|
|
20
|
+
import { printVersion, printHelp, handleUpdateCommand, handleUninstallCommand, } from "./cli/commands.js";
|
|
21
|
+
import { runServer } from "./server.js";
|
|
22
|
+
/**
|
|
23
|
+
* Main entry point for the PDF Analyzer MCP server.
|
|
24
|
+
*/
|
|
25
|
+
const main = async () => {
|
|
26
|
+
const args = process.argv.slice(2);
|
|
27
|
+
// Handle --version / -v
|
|
28
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
29
|
+
printVersion();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Handle --help / -h
|
|
33
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
34
|
+
printHelp();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Handle --update
|
|
38
|
+
if (args.includes("--update")) {
|
|
39
|
+
await handleUpdateCommand();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Handle --uninstall
|
|
43
|
+
if (args.includes("--uninstall")) {
|
|
44
|
+
await handleUninstallCommand();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Auto-update check on startup (unless --no-update or env var)
|
|
48
|
+
const skipUpdate = args.includes("--no-update") || process.env.PDF_MCP_NO_UPDATE === "1";
|
|
49
|
+
if (!skipUpdate) {
|
|
50
|
+
const updated = await autoUpdate();
|
|
51
|
+
if (updated) {
|
|
52
|
+
reexec();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
await runServer();
|
|
56
|
+
};
|
|
57
|
+
main().catch((error) => {
|
|
58
|
+
console.error("Server error:", error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EACL,YAAY,EACZ,SAAS,EACT,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;GAEG;AACH,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;IACrC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEnC,wBAAwB;IACxB,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,YAAY,EAAE,CAAC;QACf,OAAO;IACT,CAAC;IAED,qBAAqB;IACrB,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,SAAS,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,kBAAkB;IAClB,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,MAAM,mBAAmB,EAAE,CAAC;QAC5B,OAAO;IACT,CAAC;IAED,qBAAqB;IACrB,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACjC,MAAM,sBAAsB,EAAE,CAAC;QAC/B,OAAO;IACT,CAAC;IAED,+DAA+D;IAC/D,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,GAAG,CAAC;IAEzF,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,MAAM,UAAU,EAAE,CAAC;QACnC,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,EAAE,CAAC;QACX,CAAC;IACH,CAAC;IAED,MAAM,SAAS,EAAE,CAAC;AACpB,CAAC,CAAC;AAEF,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDF Analyzer MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Model Context Protocol server for analyzing PDF documents using Gemini API.
|
|
5
|
+
*/
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
/**
|
|
8
|
+
* Create and configure the MCP server.
|
|
9
|
+
*/
|
|
10
|
+
export declare const createServer: () => McpServer;
|
|
11
|
+
/**
|
|
12
|
+
* Run the MCP server with stdio transport.
|
|
13
|
+
*/
|
|
14
|
+
export declare const runServer: () => Promise<void>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDF Analyzer MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Model Context Protocol server for analyzing PDF documents using Gemini API.
|
|
5
|
+
*/
|
|
6
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { VERSION } from "./version.js";
|
|
10
|
+
import { createGeminiClient, analyzePdf, isApiError, getApiErrorMessage } from "./service.js";
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Server Instructions
|
|
13
|
+
// =============================================================================
|
|
14
|
+
const SERVER_INSTRUCTIONS = `
|
|
15
|
+
# PDF Analyzer MCP Server
|
|
16
|
+
|
|
17
|
+
Analyzes PDF documents using Gemini's vision capabilities.
|
|
18
|
+
|
|
19
|
+
## Tool: analyze_pdf
|
|
20
|
+
|
|
21
|
+
Pass an absolute file path or URL and a list of queries. The server reads the PDF,
|
|
22
|
+
sends it to Gemini with your queries, and returns structured responses.
|
|
23
|
+
|
|
24
|
+
## Caching Strategy
|
|
25
|
+
|
|
26
|
+
The response includes a \`file_uri\` (Gemini File API URI) that you should reuse for subsequent
|
|
27
|
+
queries on the same document. This avoids re-uploading and is cached by Gemini for 48 hours.
|
|
28
|
+
|
|
29
|
+
**Input types accepted:**
|
|
30
|
+
- Local file path: \`/Users/name/docs/report.pdf\`
|
|
31
|
+
- Web URL: \`https://example.com/doc.pdf\`
|
|
32
|
+
- Gemini file URI: \`https://generativelanguage.googleapis.com/v1beta/files/abc123\` (from previous response)
|
|
33
|
+
|
|
34
|
+
**Workflow for multiple queries on same document:**
|
|
35
|
+
1. First call: pass local path or URL → receive \`file_uri\` in response
|
|
36
|
+
2. Subsequent calls: pass the \`file_uri\` as \`pdf_source\` → no re-upload, faster response
|
|
37
|
+
|
|
38
|
+
## Usage Tips
|
|
39
|
+
|
|
40
|
+
- Ask specific, focused queries for best results
|
|
41
|
+
- For multi-page PDFs, reference page numbers in queries when relevant
|
|
42
|
+
- Reuse the returned \`file_uri\` for follow-up questions on the same document
|
|
43
|
+
|
|
44
|
+
## Example
|
|
45
|
+
|
|
46
|
+
\`\`\`json
|
|
47
|
+
{
|
|
48
|
+
"pdf_source": "/path/to/document.pdf",
|
|
49
|
+
"queries": [
|
|
50
|
+
"What is the main topic of this document?",
|
|
51
|
+
"List all the key findings mentioned",
|
|
52
|
+
"What recommendations are made in the conclusion?"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
\`\`\`
|
|
56
|
+
|
|
57
|
+
## Error Handling
|
|
58
|
+
|
|
59
|
+
Common errors and solutions:
|
|
60
|
+
- Missing GEMINI_API_KEY: Set the environment variable with your API key
|
|
61
|
+
- PDF not found: Verify the path is absolute and file exists
|
|
62
|
+
- URL fetch failed: Check that the URL is accessible and points to a valid PDF
|
|
63
|
+
|
|
64
|
+
## Environment Variables
|
|
65
|
+
|
|
66
|
+
- GEMINI_API_KEY: Required. Get your key from https://aistudio.google.com/apikey
|
|
67
|
+
- PDF_MCP_NO_UPDATE: Set to "1" to disable auto-updates
|
|
68
|
+
`.trim();
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Helper Functions
|
|
71
|
+
// =============================================================================
|
|
72
|
+
/**
|
|
73
|
+
* Format a result as MCP tool response content.
|
|
74
|
+
*/
|
|
75
|
+
const formatResult = (result) => {
|
|
76
|
+
const text = JSON.stringify(result, null, 2);
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: "text", text }],
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Format an error as MCP tool response content.
|
|
83
|
+
*/
|
|
84
|
+
const formatError = (error, details) => {
|
|
85
|
+
const result = details ? { error, details } : { error };
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Server Setup
|
|
93
|
+
// =============================================================================
|
|
94
|
+
/**
|
|
95
|
+
* Create and configure the MCP server.
|
|
96
|
+
*/
|
|
97
|
+
export const createServer = () => {
|
|
98
|
+
const server = new McpServer({
|
|
99
|
+
name: "pdf-analyzer",
|
|
100
|
+
version: VERSION,
|
|
101
|
+
}, {
|
|
102
|
+
capabilities: {
|
|
103
|
+
tools: {},
|
|
104
|
+
},
|
|
105
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
106
|
+
});
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
// Tool: analyze_pdf
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
server.registerTool("analyze_pdf", {
|
|
111
|
+
description: "Analyze a PDF document using Gemini AI. Provide an absolute file path, URL, or Gemini file URI (from a previous response) and a list of questions to ask about the PDF content. Returns a file_uri that can be reused for subsequent queries on the same document (cached by Gemini for 48 hours).",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
pdf_source: z
|
|
114
|
+
.string()
|
|
115
|
+
.describe("PDF source: absolute local file path (e.g., /Users/name/docs/report.pdf), URL (e.g., https://example.com/doc.pdf), or Gemini file URI from a previous response (e.g., https://generativelanguage.googleapis.com/v1beta/files/abc123)"),
|
|
116
|
+
queries: z.array(z.string().min(1)).min(1).describe("Array of questions to ask about the PDF"),
|
|
117
|
+
},
|
|
118
|
+
}, async ({ pdf_source, queries }) => {
|
|
119
|
+
try {
|
|
120
|
+
const client = createGeminiClient();
|
|
121
|
+
const result = await analyzePdf(client, { pdf_source, queries });
|
|
122
|
+
return formatResult(result);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
// Handle typed Gemini API errors
|
|
126
|
+
if (isApiError(error)) {
|
|
127
|
+
const { message, details } = getApiErrorMessage(error);
|
|
128
|
+
return formatError(message, details);
|
|
129
|
+
}
|
|
130
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
131
|
+
// Provide helpful context for common errors
|
|
132
|
+
if (message.includes("GEMINI_API_KEY")) {
|
|
133
|
+
return formatError(message, "Set the GEMINI_API_KEY environment variable in your MCP client configuration.");
|
|
134
|
+
}
|
|
135
|
+
if (message.includes("not found")) {
|
|
136
|
+
return formatError(message, "Ensure the path is absolute and the file exists.");
|
|
137
|
+
}
|
|
138
|
+
if (message.includes("Failed to fetch PDF from URL")) {
|
|
139
|
+
return formatError(message, "Check that the URL is accessible and points to a valid PDF file.");
|
|
140
|
+
}
|
|
141
|
+
return formatError(message);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return server;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Run the MCP server with stdio transport.
|
|
148
|
+
*/
|
|
149
|
+
export const runServer = async () => {
|
|
150
|
+
const server = createServer();
|
|
151
|
+
const transport = new StdioServerTransport();
|
|
152
|
+
await server.connect(transport);
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { GoogleGenAI, ApiError } from "@google/genai";
|
|
2
|
+
import type { AnalyzePdfInput, AnalyzePdfResponse } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Creates and returns a configured GoogleGenAI client.
|
|
5
|
+
* Loads GEMINI_API_KEY from .env file if not already set in environment.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createGeminiClient(): GoogleGenAI;
|
|
8
|
+
/**
|
|
9
|
+
* Check if a string is a Gemini File API URI.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isGeminiFileUri(source: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a string is a URL (excluding Gemini File API URIs).
|
|
14
|
+
*/
|
|
15
|
+
export declare function isUrl(source: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Validates a local PDF file path.
|
|
18
|
+
* Throws descriptive errors for common issues.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateLocalPath(pdfPath: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Analyzes a PDF document using Gemini and returns responses to the provided queries.
|
|
23
|
+
* Uses the File API for uploads and structured output for reliable JSON responses.
|
|
24
|
+
* Returns the Gemini file_uri so calling agents can reuse it for subsequent queries.
|
|
25
|
+
*/
|
|
26
|
+
export declare function analyzePdf(client: GoogleGenAI, input: AnalyzePdfInput): Promise<AnalyzePdfResponse>;
|
|
27
|
+
/**
|
|
28
|
+
* Check if an error is an ApiError and return typed error info.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isApiError(error: unknown): error is ApiError;
|
|
31
|
+
/**
|
|
32
|
+
* Get error message from ApiError, preserving the actual API response.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getApiErrorMessage(error: ApiError): {
|
|
35
|
+
message: string;
|
|
36
|
+
details?: string;
|
|
37
|
+
};
|