@papermark/mcp-server 0.9.0 → 0.9.1
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 +25 -7
- package/dist/{chunk-STGR2RDC.js → chunk-3I6OOD2X.js} +6 -2
- package/dist/chunk-3I6OOD2X.js.map +1 -0
- package/dist/index.js +213 -35
- package/dist/index.js.map +1 -1
- package/dist/tools-contract.d.ts +94 -36
- package/dist/tools-contract.js +11 -3
- package/package.json +2 -1
- package/dist/chunk-STGR2RDC.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -58,12 +58,23 @@ import {
|
|
|
58
58
|
updateDocumentContract,
|
|
59
59
|
updateFolderContract,
|
|
60
60
|
updateLinkContract
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-3I6OOD2X.js";
|
|
62
62
|
|
|
63
63
|
// src/index.ts
|
|
64
64
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
65
65
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
66
66
|
|
|
67
|
+
// src/client-info.ts
|
|
68
|
+
var hostName;
|
|
69
|
+
var hostVersion;
|
|
70
|
+
function setClientHost(name, version) {
|
|
71
|
+
hostName = name?.trim() || void 0;
|
|
72
|
+
hostVersion = version?.trim() || void 0;
|
|
73
|
+
}
|
|
74
|
+
function getClientHost() {
|
|
75
|
+
return { name: hostName, version: hostVersion };
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
// src/tools.ts
|
|
68
79
|
import { readFile, stat } from "fs/promises";
|
|
69
80
|
import { basename } from "path";
|
|
@@ -79,6 +90,104 @@ function getApiUrl() {
|
|
|
79
90
|
return process.env.PAPERMARK_API_URL?.trim() || DEFAULT_API_URL;
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
// src/debug.ts
|
|
94
|
+
var enabled = process.env.PAPERMARK_DEBUG === "1" || process.env.PAPERMARK_DEBUG === "true";
|
|
95
|
+
function debug(category, ...parts) {
|
|
96
|
+
if (!enabled) return;
|
|
97
|
+
const line = parts.map(safeStringify).join(" ");
|
|
98
|
+
process.stderr.write(`[papermark-mcp:${category}] ${line}
|
|
99
|
+
`);
|
|
100
|
+
}
|
|
101
|
+
function safeStringify(p) {
|
|
102
|
+
if (typeof p === "string") return p;
|
|
103
|
+
try {
|
|
104
|
+
return JSON.stringify(p) ?? String(p);
|
|
105
|
+
} catch {
|
|
106
|
+
return String(p);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// package.json
|
|
111
|
+
var package_default = {
|
|
112
|
+
name: "@papermark/mcp-server",
|
|
113
|
+
version: "0.9.1",
|
|
114
|
+
description: "Model Context Protocol server for Papermark \u2014 give AI assistants access to your data rooms, documents, share links, and analytics.",
|
|
115
|
+
license: "MIT",
|
|
116
|
+
homepage: "https://www.papermark.com",
|
|
117
|
+
repository: {
|
|
118
|
+
type: "git",
|
|
119
|
+
url: "git+https://github.com/papermark/papermark-mcp-server.git"
|
|
120
|
+
},
|
|
121
|
+
bugs: {
|
|
122
|
+
url: "https://github.com/papermark/papermark-mcp-server/issues"
|
|
123
|
+
},
|
|
124
|
+
keywords: [
|
|
125
|
+
"papermark",
|
|
126
|
+
"dataroom",
|
|
127
|
+
"virtual-dataroom",
|
|
128
|
+
"datarooms",
|
|
129
|
+
"mcp",
|
|
130
|
+
"model-context-protocol",
|
|
131
|
+
"agents",
|
|
132
|
+
"documents"
|
|
133
|
+
],
|
|
134
|
+
type: "module",
|
|
135
|
+
bin: {
|
|
136
|
+
"papermark-mcp": "dist/index.js"
|
|
137
|
+
},
|
|
138
|
+
exports: {
|
|
139
|
+
".": {
|
|
140
|
+
types: "./dist/index.d.ts",
|
|
141
|
+
default: "./dist/index.js"
|
|
142
|
+
},
|
|
143
|
+
"./tools-contract": {
|
|
144
|
+
types: "./dist/tools-contract.d.ts",
|
|
145
|
+
default: "./dist/tools-contract.js"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
publishConfig: {
|
|
149
|
+
access: "public"
|
|
150
|
+
},
|
|
151
|
+
files: [
|
|
152
|
+
"dist",
|
|
153
|
+
"README.md",
|
|
154
|
+
"LICENSE"
|
|
155
|
+
],
|
|
156
|
+
engines: {
|
|
157
|
+
node: ">=24"
|
|
158
|
+
},
|
|
159
|
+
scripts: {
|
|
160
|
+
build: "tsup",
|
|
161
|
+
dev: "tsx src/index.ts",
|
|
162
|
+
typecheck: "tsc --noEmit",
|
|
163
|
+
test: "node --import tsx --test test/*.test.ts",
|
|
164
|
+
smoke: "npm run build && node scripts/smoke.mjs",
|
|
165
|
+
changeset: "changeset",
|
|
166
|
+
version: "changeset version",
|
|
167
|
+
release: "npm run build && changeset publish",
|
|
168
|
+
prepublishOnly: "npm run build"
|
|
169
|
+
},
|
|
170
|
+
dependencies: {
|
|
171
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
172
|
+
"mime-types": "^3.0.2",
|
|
173
|
+
zod: "^4.4.3"
|
|
174
|
+
},
|
|
175
|
+
devDependencies: {
|
|
176
|
+
"@changesets/changelog-github": "^0.7.0",
|
|
177
|
+
"@changesets/cli": "^2.31.0",
|
|
178
|
+
"@types/mime-types": "^3.0.1",
|
|
179
|
+
"@types/node": "^24.12.4",
|
|
180
|
+
tsup: "^8.3.5",
|
|
181
|
+
tsx: "^4.21.0",
|
|
182
|
+
typescript: "^5.7.2"
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/version.ts
|
|
187
|
+
var VERSION = package_default.version;
|
|
188
|
+
var CLIENT_ID = "mcp";
|
|
189
|
+
var USER_AGENT = `papermark-mcp/${VERSION} (node ${process.version}; ${process.platform} ${process.arch})`;
|
|
190
|
+
|
|
82
191
|
// src/client.ts
|
|
83
192
|
var ApiError = class extends Error {
|
|
84
193
|
code;
|
|
@@ -146,9 +255,29 @@ function buildUrl(path, query) {
|
|
|
146
255
|
}
|
|
147
256
|
return url.toString();
|
|
148
257
|
}
|
|
258
|
+
function isSafeHeaderValue(v) {
|
|
259
|
+
return !/[\u0000-\u001F\u007F]/.test(v);
|
|
260
|
+
}
|
|
261
|
+
function clientHostHeaders() {
|
|
262
|
+
const { name, version } = getClientHost();
|
|
263
|
+
const out = {};
|
|
264
|
+
if (name && isSafeHeaderValue(name)) out["x-papermark-client-host"] = name;
|
|
265
|
+
if (version && isSafeHeaderValue(version)) {
|
|
266
|
+
out["x-papermark-client-host-version"] = version;
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
149
270
|
async function request(path, opts = {}) {
|
|
150
271
|
const headers = {
|
|
151
272
|
Accept: "application/json",
|
|
273
|
+
// Identify the caller so the API can attribute usage to the MCP server
|
|
274
|
+
// (vs the CLI or raw HTTP), its version, and the host the agent runs in
|
|
275
|
+
// (Claude Desktop, Cursor, …). Caller-supplied headers still win via the
|
|
276
|
+
// spread below.
|
|
277
|
+
"User-Agent": USER_AGENT,
|
|
278
|
+
"x-papermark-client": CLIENT_ID,
|
|
279
|
+
"x-papermark-client-version": VERSION,
|
|
280
|
+
...clientHostHeaders(),
|
|
152
281
|
...opts.headers ?? {}
|
|
153
282
|
};
|
|
154
283
|
if (!opts.anonymous) {
|
|
@@ -166,40 +295,63 @@ async function request(path, opts = {}) {
|
|
|
166
295
|
}
|
|
167
296
|
}
|
|
168
297
|
const url = buildUrl(path, opts.query);
|
|
298
|
+
const method = opts.method ?? (opts.body ? "POST" : "GET");
|
|
169
299
|
const controller = new AbortController();
|
|
170
300
|
const timeoutMs = opts.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
171
301
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
172
|
-
|
|
302
|
+
const started = Date.now();
|
|
303
|
+
debug("http", method, url, body ? "(body)" : "");
|
|
304
|
+
const asTimeout = (err) => controller.signal.aborted || err instanceof Error && err.name === "AbortError";
|
|
173
305
|
try {
|
|
174
|
-
res
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
306
|
+
let res;
|
|
307
|
+
try {
|
|
308
|
+
res = await fetch(url, {
|
|
309
|
+
method,
|
|
310
|
+
headers,
|
|
311
|
+
body,
|
|
312
|
+
signal: controller.signal
|
|
313
|
+
});
|
|
314
|
+
} catch (err) {
|
|
315
|
+
if (asTimeout(err)) {
|
|
316
|
+
debug("http", method, url, "timeout after", `${timeoutMs}ms`);
|
|
317
|
+
throw new NetworkError(
|
|
318
|
+
url,
|
|
319
|
+
new Error(`Request exceeded ${timeoutMs}ms timeout`)
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
debug("http", "network error after", `${Date.now() - started}ms`, String(err));
|
|
323
|
+
throw new NetworkError(url, err);
|
|
324
|
+
}
|
|
325
|
+
const requestId = res.headers.get("x-request-id") ?? "";
|
|
326
|
+
debug("http", method, url, `\u2192 ${res.status}`, requestId, `(${Date.now() - started}ms)`);
|
|
327
|
+
if (res.status === 204) return void 0;
|
|
328
|
+
let text;
|
|
329
|
+
try {
|
|
330
|
+
text = await res.text();
|
|
331
|
+
} catch (err) {
|
|
332
|
+
if (asTimeout(err)) {
|
|
333
|
+
debug("http", method, url, "timeout reading body after", `${timeoutMs}ms`);
|
|
334
|
+
throw new NetworkError(
|
|
335
|
+
url,
|
|
336
|
+
new Error(`Request exceeded ${timeoutMs}ms timeout`)
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
debug("http", "body read error after", `${Date.now() - started}ms`, String(err));
|
|
340
|
+
throw new NetworkError(url, err);
|
|
186
341
|
}
|
|
187
|
-
|
|
342
|
+
const parsed = text ? safeParseJson(text) : null;
|
|
343
|
+
if (!res.ok) {
|
|
344
|
+
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
345
|
+
throw new ApiError(res.status, parsed.error);
|
|
346
|
+
}
|
|
347
|
+
const looksLikeHtml = text.trimStart().toLowerCase().startsWith("<");
|
|
348
|
+
const msg = looksLikeHtml ? `HTTP ${res.status} (server returned HTML, not JSON \u2014 is the API reachable?)` : text || `HTTP ${res.status}`;
|
|
349
|
+
throw new ApiError(res.status, msg);
|
|
350
|
+
}
|
|
351
|
+
return parsed ?? void 0;
|
|
188
352
|
} finally {
|
|
189
353
|
clearTimeout(timer);
|
|
190
354
|
}
|
|
191
|
-
if (res.status === 204) return void 0;
|
|
192
|
-
const text = await res.text();
|
|
193
|
-
const parsed = text ? safeParseJson(text) : null;
|
|
194
|
-
if (!res.ok) {
|
|
195
|
-
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
196
|
-
throw new ApiError(res.status, parsed.error);
|
|
197
|
-
}
|
|
198
|
-
const looksLikeHtml = text.trimStart().toLowerCase().startsWith("<");
|
|
199
|
-
const msg = looksLikeHtml ? `HTTP ${res.status} (server returned HTML, not JSON \u2014 is the API reachable?)` : text || `HTTP ${res.status}`;
|
|
200
|
-
throw new ApiError(res.status, msg);
|
|
201
|
-
}
|
|
202
|
-
return parsed ?? void 0;
|
|
203
355
|
}
|
|
204
356
|
function safeParseJson(s) {
|
|
205
357
|
try {
|
|
@@ -224,7 +376,12 @@ var api = {
|
|
|
224
376
|
body,
|
|
225
377
|
token
|
|
226
378
|
}),
|
|
227
|
-
createDocument: (body, token) => request("/v1/documents", {
|
|
379
|
+
createDocument: (body, token, opts) => request("/v1/documents", {
|
|
380
|
+
method: "POST",
|
|
381
|
+
body,
|
|
382
|
+
token,
|
|
383
|
+
timeout: opts?.timeout
|
|
384
|
+
}),
|
|
228
385
|
// Document versions
|
|
229
386
|
listDocumentVersions: (id, token) => request(`/v1/documents/${id}/versions`, { token }),
|
|
230
387
|
addDocumentVersion: (id, body, token) => request(`/v1/documents/${id}/versions`, {
|
|
@@ -445,8 +602,11 @@ function toStructuredContent(result) {
|
|
|
445
602
|
return { result };
|
|
446
603
|
}
|
|
447
604
|
async function run(fn, label) {
|
|
605
|
+
const started = Date.now();
|
|
606
|
+
debug("tool", label, "called");
|
|
448
607
|
try {
|
|
449
608
|
const result = await fn();
|
|
609
|
+
debug("tool", label, "ok", `(${Date.now() - started}ms)`);
|
|
450
610
|
const structuredContent = toStructuredContent(result);
|
|
451
611
|
return {
|
|
452
612
|
structuredContent,
|
|
@@ -455,6 +615,13 @@ async function run(fn, label) {
|
|
|
455
615
|
]
|
|
456
616
|
};
|
|
457
617
|
} catch (err) {
|
|
618
|
+
debug(
|
|
619
|
+
"tool",
|
|
620
|
+
label,
|
|
621
|
+
"error",
|
|
622
|
+
err instanceof Error ? err.name : "unknown",
|
|
623
|
+
`(${Date.now() - started}ms)`
|
|
624
|
+
);
|
|
458
625
|
if (err instanceof ApiError) {
|
|
459
626
|
const detail = err.details !== void 0 && err.details !== null ? `
|
|
460
627
|
Details: ${JSON.stringify(err.details)}` : "";
|
|
@@ -643,12 +810,19 @@ Don't add any source the user didn't explicitly point at. Requires scope \`docum
|
|
|
643
810
|
}
|
|
644
811
|
if (args.source_url) {
|
|
645
812
|
const name = args.name ?? filenameFromUrl(args.source_url);
|
|
646
|
-
const doc2 = await api.createDocument(
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
813
|
+
const doc2 = await api.createDocument(
|
|
814
|
+
{
|
|
815
|
+
name,
|
|
816
|
+
source_url: args.source_url,
|
|
817
|
+
folder_id: args.folder_id,
|
|
818
|
+
create_link: args.create_link
|
|
819
|
+
},
|
|
820
|
+
void 0,
|
|
821
|
+
// The server streams up to 100 MB from source_url synchronously
|
|
822
|
+
// before responding; give it the same 5-minute budget as the
|
|
823
|
+
// file_path PUT so a slow-but-healthy import isn't aborted at 60s.
|
|
824
|
+
{ timeout: 5 * 6e4 }
|
|
825
|
+
);
|
|
652
826
|
return { document: doc2 };
|
|
653
827
|
}
|
|
654
828
|
const filePath = args.file_path;
|
|
@@ -1462,11 +1636,15 @@ Don't add any source the user didn't explicitly point at. Requires scope \`docum
|
|
|
1462
1636
|
// src/index.ts
|
|
1463
1637
|
var SERVER_INFO = {
|
|
1464
1638
|
name: "papermark-mcp",
|
|
1465
|
-
version:
|
|
1639
|
+
version: VERSION
|
|
1466
1640
|
};
|
|
1467
1641
|
async function main() {
|
|
1468
1642
|
const server = new McpServer(SERVER_INFO, { capabilities: { tools: {} } });
|
|
1469
1643
|
registerTools(server);
|
|
1644
|
+
server.server.oninitialized = () => {
|
|
1645
|
+
const client = server.server.getClientVersion();
|
|
1646
|
+
setClientHost(client?.name, client?.version);
|
|
1647
|
+
};
|
|
1470
1648
|
const transport = new StdioServerTransport();
|
|
1471
1649
|
await server.connect(transport);
|
|
1472
1650
|
}
|