notebooklm-mcp-server 1.1.9 → 2.0.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/dist/client.d.ts +12 -2
- package/dist/client.js +124 -39
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.js +2 -2
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -10,14 +10,24 @@ export declare class NotebookLMClient {
|
|
|
10
10
|
private client;
|
|
11
11
|
private csrfToken;
|
|
12
12
|
private sessionId;
|
|
13
|
+
private initialized;
|
|
13
14
|
constructor(cookies: string);
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* Initialize CSRF token and session ID from the main page.
|
|
17
|
+
* Must be called before any RPC call.
|
|
18
|
+
*/
|
|
19
|
+
init(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Internal RPC executor using the standard Google batchexecute format.
|
|
16
22
|
*/
|
|
17
23
|
private callRpc;
|
|
18
24
|
/**
|
|
19
25
|
* Parses the weird batchexecute envelope format.
|
|
20
26
|
*/
|
|
27
|
+
/**
|
|
28
|
+
* Parses the Google batchexecute chunked response format.
|
|
29
|
+
* Response format: )]}'\n<bytecount>\n[["wrb.fr","rpcId","<json>",null,...]]\n...
|
|
30
|
+
*/
|
|
21
31
|
private parseBatchResponse;
|
|
22
32
|
listNotebooks(): Promise<Notebook[]>;
|
|
23
33
|
createNotebook(title: string): Promise<string>;
|
|
@@ -48,7 +58,7 @@ export declare class NotebookLMClient {
|
|
|
48
58
|
private parseQueryResponse;
|
|
49
59
|
private extractFromChunk;
|
|
50
60
|
/**
|
|
51
|
-
*
|
|
61
|
+
* Force re-fetch of CSRF token and session ID.
|
|
52
62
|
*/
|
|
53
63
|
refreshTokens(): Promise<void>;
|
|
54
64
|
}
|
package/dist/client.js
CHANGED
|
@@ -16,6 +16,7 @@ export class NotebookLMClient {
|
|
|
16
16
|
client;
|
|
17
17
|
csrfToken = null;
|
|
18
18
|
sessionId = null;
|
|
19
|
+
initialized = false;
|
|
19
20
|
constructor(cookies) {
|
|
20
21
|
this.client = axios.create({
|
|
21
22
|
baseURL: BASE_URL,
|
|
@@ -27,37 +28,70 @@ export class NotebookLMClient {
|
|
|
27
28
|
});
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
+
* Initialize CSRF token and session ID from the main page.
|
|
32
|
+
* Must be called before any RPC call.
|
|
33
|
+
*/
|
|
34
|
+
async init() {
|
|
35
|
+
if (this.initialized)
|
|
36
|
+
return;
|
|
37
|
+
try {
|
|
38
|
+
const response = await this.client.get('/');
|
|
39
|
+
const csrfMatch = response.data.match(/"SNlM0e"\s*:\s*"([^"]+)"/);
|
|
40
|
+
if (csrfMatch) {
|
|
41
|
+
this.csrfToken = csrfMatch[1];
|
|
42
|
+
}
|
|
43
|
+
const sidMatch = response.data.match(/"FdrFJe"\s*:\s*"([^"]+)"/);
|
|
44
|
+
if (sidMatch) {
|
|
45
|
+
this.sessionId = sidMatch[1];
|
|
46
|
+
}
|
|
47
|
+
this.initialized = true;
|
|
48
|
+
if (!this.csrfToken) {
|
|
49
|
+
console.error('[NotebookLM] Warning: Could not extract CSRF token. Authentication may be expired.');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
console.error('[NotebookLM] Failed to initialize session:', e.message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Internal RPC executor using the standard Google batchexecute format.
|
|
31
58
|
*/
|
|
32
59
|
async callRpc(rpcId, params, _retryCount = 0) {
|
|
33
|
-
//
|
|
34
|
-
|
|
60
|
+
// Ensure we have CSRF token before making any call
|
|
61
|
+
await this.init();
|
|
62
|
+
// Standard Google batchexecute envelope: [[[rpcId, paramsJson, null, "generic"]]]
|
|
63
|
+
const paramsJson = JSON.stringify(params);
|
|
64
|
+
const fReq = JSON.stringify([[[rpcId, paramsJson, null, "generic"]]]);
|
|
35
65
|
const body = new URLSearchParams();
|
|
36
66
|
body.append('f.req', fReq);
|
|
37
67
|
if (this.csrfToken) {
|
|
38
68
|
body.append('at', this.csrfToken);
|
|
39
69
|
}
|
|
70
|
+
const queryParams = {
|
|
71
|
+
'rpcids': rpcId,
|
|
72
|
+
'source-path': '/',
|
|
73
|
+
'bl': 'boq_labs-tailwind-frontend_20260108.06_p0',
|
|
74
|
+
'hl': 'en',
|
|
75
|
+
'_reqid': Math.floor(Math.random() * 900000 + 100000).toString(),
|
|
76
|
+
'rt': 'c'
|
|
77
|
+
};
|
|
78
|
+
if (this.sessionId) {
|
|
79
|
+
queryParams['f.sid'] = this.sessionId;
|
|
80
|
+
}
|
|
40
81
|
try {
|
|
41
82
|
const response = await this.client.post(BATCH_EXECUTE_PATH, body.toString(), {
|
|
42
|
-
params:
|
|
43
|
-
'rpcids': rpcId,
|
|
44
|
-
'source-path': '/',
|
|
45
|
-
'f.sid': this.sessionId,
|
|
46
|
-
'bl': 'boq_labs-tailwind-frontend_20260108.06_p0',
|
|
47
|
-
'hl': 'en',
|
|
48
|
-
'_reqid': Math.floor(Math.random() * 1000000).toString(),
|
|
49
|
-
'rt': 'c'
|
|
50
|
-
}
|
|
83
|
+
params: queryParams
|
|
51
84
|
});
|
|
52
|
-
// Special case: Google might return success but the body indicates an internal auth failure
|
|
53
|
-
// (usually represented by specific error codes in the response array)
|
|
54
|
-
if (typeof response.data === 'string' && response.data.includes('session expired')) {
|
|
55
|
-
throw new AuthenticationError('Session expired in response body');
|
|
56
|
-
}
|
|
57
85
|
const rpcResult = this.parseBatchResponse(response.data, rpcId);
|
|
58
|
-
if (rpcResult === null
|
|
59
|
-
|
|
60
|
-
|
|
86
|
+
if (rpcResult === null) {
|
|
87
|
+
const dataStr = typeof response.data === 'string' ? response.data : '';
|
|
88
|
+
// Check for error envelope from Google
|
|
89
|
+
if (dataStr.includes('"er"')) {
|
|
90
|
+
throw new AuthenticationError('Google returned an error. Session may be expired.');
|
|
91
|
+
}
|
|
92
|
+
if (!dataStr.includes(rpcId)) {
|
|
93
|
+
throw new AuthenticationError('Invalid session or session expired (RPC ID not in response)');
|
|
94
|
+
}
|
|
61
95
|
}
|
|
62
96
|
return rpcResult;
|
|
63
97
|
}
|
|
@@ -66,12 +100,13 @@ export class NotebookLMClient {
|
|
|
66
100
|
error.response?.status === 401 ||
|
|
67
101
|
error.response?.status === 403;
|
|
68
102
|
if (isAuthError && _retryCount < 2) {
|
|
69
|
-
console.error(`Auth failure
|
|
70
|
-
|
|
103
|
+
console.error(`[NotebookLM] Auth failure. Refreshing tokens (attempt ${_retryCount + 1})...`);
|
|
104
|
+
this.initialized = false;
|
|
105
|
+
await this.init();
|
|
71
106
|
return this.callRpc(rpcId, params, _retryCount + 1);
|
|
72
107
|
}
|
|
73
108
|
if (isAuthError) {
|
|
74
|
-
throw new AuthenticationError('Authentication failed
|
|
109
|
+
throw new AuthenticationError('Authentication failed. Please run: notebooklm-mcp-server auth');
|
|
75
110
|
}
|
|
76
111
|
throw error;
|
|
77
112
|
}
|
|
@@ -79,24 +114,75 @@ export class NotebookLMClient {
|
|
|
79
114
|
/**
|
|
80
115
|
* Parses the weird batchexecute envelope format.
|
|
81
116
|
*/
|
|
117
|
+
/**
|
|
118
|
+
* Parses the Google batchexecute chunked response format.
|
|
119
|
+
* Response format: )]}'\n<bytecount>\n[["wrb.fr","rpcId","<json>",null,...]]\n...
|
|
120
|
+
*/
|
|
82
121
|
parseBatchResponse(data, rpcId) {
|
|
83
|
-
// Google's format is basically a set of chunked JSON arrays
|
|
84
|
-
// We need to extract the payload for the given rpcId
|
|
85
122
|
try {
|
|
86
|
-
|
|
123
|
+
let dataStr = typeof data === 'string' ? data : JSON.stringify(data);
|
|
124
|
+
// Strip anti-XSSI prefix
|
|
125
|
+
if (dataStr.startsWith(")]}'\n")) {
|
|
126
|
+
dataStr = dataStr.substring(5);
|
|
127
|
+
}
|
|
128
|
+
else if (dataStr.startsWith(")]}'\r\n")) {
|
|
129
|
+
dataStr = dataStr.substring(6);
|
|
130
|
+
}
|
|
131
|
+
// Parse chunked format: alternating byte_count + json_payload lines
|
|
87
132
|
const lines = dataStr.split('\n');
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
133
|
+
let i = 0;
|
|
134
|
+
while (i < lines.length) {
|
|
135
|
+
const line = lines[i].trim();
|
|
136
|
+
if (!line) {
|
|
137
|
+
i++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
let jsonLine = null;
|
|
141
|
+
// Check if this is a byte count (next line is the JSON)
|
|
142
|
+
if (/^\d+$/.test(line)) {
|
|
143
|
+
i++;
|
|
144
|
+
if (i < lines.length) {
|
|
145
|
+
jsonLine = lines[i].trim();
|
|
94
146
|
}
|
|
95
147
|
}
|
|
148
|
+
else {
|
|
149
|
+
jsonLine = line;
|
|
150
|
+
}
|
|
151
|
+
if (jsonLine) {
|
|
152
|
+
try {
|
|
153
|
+
const chunk = JSON.parse(jsonLine);
|
|
154
|
+
// chunk is an array of items like ["wrb.fr", rpcId, data, ...]
|
|
155
|
+
const items = Array.isArray(chunk) && Array.isArray(chunk[0]) ? chunk : [chunk];
|
|
156
|
+
for (const item of items) {
|
|
157
|
+
if (!Array.isArray(item) || item.length < 3)
|
|
158
|
+
continue;
|
|
159
|
+
// Error response
|
|
160
|
+
if (item[0] === 'er' && item[1] === rpcId) {
|
|
161
|
+
throw new Error(`Google RPC error for ${rpcId}: ${JSON.stringify(item[2])}`);
|
|
162
|
+
}
|
|
163
|
+
// Success response
|
|
164
|
+
if (item[0] === 'wrb.fr' && item[1] === rpcId) {
|
|
165
|
+
const resultData = item[2];
|
|
166
|
+
if (typeof resultData === 'string') {
|
|
167
|
+
return JSON.parse(resultData);
|
|
168
|
+
}
|
|
169
|
+
return resultData;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (parseErr) {
|
|
174
|
+
if (parseErr.message?.startsWith('Google RPC error'))
|
|
175
|
+
throw parseErr;
|
|
176
|
+
// Not valid JSON, skip
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
i++;
|
|
96
180
|
}
|
|
97
181
|
}
|
|
98
182
|
catch (e) {
|
|
99
|
-
|
|
183
|
+
if (e.message?.startsWith('Google RPC error'))
|
|
184
|
+
throw e;
|
|
185
|
+
console.error('[NotebookLM] Failed to parse RPC response:', e.message);
|
|
100
186
|
}
|
|
101
187
|
return null;
|
|
102
188
|
}
|
|
@@ -308,6 +394,8 @@ export class NotebookLMClient {
|
|
|
308
394
|
* Complex query method with streaming support.
|
|
309
395
|
*/
|
|
310
396
|
async query(notebookId, queryText, sourceIds, conversationId) {
|
|
397
|
+
// Ensure tokens are available
|
|
398
|
+
await this.init();
|
|
311
399
|
const cid = conversationId || uuidv4();
|
|
312
400
|
const sources = sourceIds ? sourceIds.map(id => [[id]]) : [];
|
|
313
401
|
// Structure matching Python: [sources_array, query_text, history, [2, null, [1]], conversation_id]
|
|
@@ -416,13 +504,10 @@ export class NotebookLMClient {
|
|
|
416
504
|
return { text: null, isAnswer: false };
|
|
417
505
|
}
|
|
418
506
|
/**
|
|
419
|
-
*
|
|
507
|
+
* Force re-fetch of CSRF token and session ID.
|
|
420
508
|
*/
|
|
421
509
|
async refreshTokens() {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (match) {
|
|
425
|
-
this.csrfToken = match[1];
|
|
426
|
-
}
|
|
510
|
+
this.initialized = false;
|
|
511
|
+
await this.init();
|
|
427
512
|
}
|
|
428
513
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -27,6 +27,6 @@ export declare const RPC_IDS: {
|
|
|
27
27
|
DELETE_MIND_MAP: string;
|
|
28
28
|
};
|
|
29
29
|
export declare const BASE_URL = "https://notebooklm.google.com";
|
|
30
|
-
export declare const BATCH_EXECUTE_PATH = "/_/
|
|
31
|
-
export declare const QUERY_PATH = "/_/
|
|
30
|
+
export declare const BATCH_EXECUTE_PATH = "/_/LabsTailwindUi/data/batchexecute";
|
|
31
|
+
export declare const QUERY_PATH = "/_/LabsTailwindUi/data/batchexecute";
|
|
32
32
|
export declare const DEFAULT_QUERY_TIMEOUT = 120000;
|
package/dist/constants.js
CHANGED
|
@@ -27,6 +27,6 @@ export const RPC_IDS = {
|
|
|
27
27
|
DELETE_MIND_MAP: "AH0mwd",
|
|
28
28
|
};
|
|
29
29
|
export const BASE_URL = "https://notebooklm.google.com";
|
|
30
|
-
export const BATCH_EXECUTE_PATH = "/_/
|
|
31
|
-
export const QUERY_PATH = "/_/
|
|
30
|
+
export const BATCH_EXECUTE_PATH = "/_/LabsTailwindUi/data/batchexecute";
|
|
31
|
+
export const QUERY_PATH = "/_/LabsTailwindUi/data/batchexecute";
|
|
32
32
|
export const DEFAULT_QUERY_TIMEOUT = 120000; // 120s
|
package/dist/index.js
CHANGED
package/dist/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { AuthManager } from "./auth.js";
|
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
const server = new Server({
|
|
8
8
|
name: "notebooklm-mcp-server",
|
|
9
|
-
version: "
|
|
9
|
+
version: "2.0.0",
|
|
10
10
|
}, {
|
|
11
11
|
capabilities: {
|
|
12
12
|
tools: {},
|
|
@@ -364,7 +364,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
364
364
|
async function main() {
|
|
365
365
|
const transport = new StdioServerTransport();
|
|
366
366
|
await server.connect(transport);
|
|
367
|
-
console.error("NotebookLM MCP Server
|
|
367
|
+
console.error("NotebookLM MCP Server v2.0.0 running on stdio");
|
|
368
368
|
}
|
|
369
369
|
main().catch((error) => {
|
|
370
370
|
console.error("Fatal error:", error);
|