@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/dist/index.js CHANGED
@@ -58,12 +58,23 @@ import {
58
58
  updateDocumentContract,
59
59
  updateFolderContract,
60
60
  updateLinkContract
61
- } from "./chunk-STGR2RDC.js";
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
- let res;
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 = await fetch(url, {
175
- method: opts.method ?? (opts.body ? "POST" : "GET"),
176
- headers,
177
- body,
178
- signal: controller.signal
179
- });
180
- } catch (err) {
181
- if (controller.signal.aborted || err instanceof Error && err.name === "AbortError") {
182
- throw new NetworkError(
183
- url,
184
- new Error(`Request exceeded ${timeoutMs}ms timeout`)
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
- throw new NetworkError(url, err);
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", { method: "POST", body, token }),
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
- name,
648
- source_url: args.source_url,
649
- folder_id: args.folder_id,
650
- create_link: args.create_link
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: "0.1.0"
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
  }