@dyingc/brave-search-mcp-server 2.0.69 → 2.0.70
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 +42 -0
- package/dist/utils/apiKeyManager.js +65 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,6 +129,48 @@ The server supports the following environment variables:
|
|
|
129
129
|
- `BRAVE_MCP_RETRY_BASE_DELAY`: Base delay for retry in milliseconds (default: 1000, range: 100-60000)
|
|
130
130
|
- `BRAVE_MCP_RETRY_MAX_DELAY`: Maximum delay for retry in milliseconds (default: 30000, range: 1000-300000)
|
|
131
131
|
|
|
132
|
+
### Multiple API Keys
|
|
133
|
+
|
|
134
|
+
When using multiple API keys, the server automatically load balances requests across keys based on remaining monthly quota. Keys with more available quota are prioritized.
|
|
135
|
+
|
|
136
|
+
**Environment Variables for Multiple Keys:**
|
|
137
|
+
|
|
138
|
+
1. `BRAVE_API_KEYS`: Comma-separated list of API keys
|
|
139
|
+
```bash
|
|
140
|
+
export BRAVE_API_KEYS="key1,key2,key3"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
2. Numbered variables (legacy):
|
|
144
|
+
```bash
|
|
145
|
+
export BRAVE_API_KEY_1="key1"
|
|
146
|
+
export BRAVE_API_KEY_2="key2"
|
|
147
|
+
export BRAVE_API_KEY_3="key3"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**State Persistence:**
|
|
151
|
+
|
|
152
|
+
The server persists API key quota state to `~/.brave-mcp/state.json` to maintain consistent load balancing across server restarts:
|
|
153
|
+
|
|
154
|
+
- State is saved after each API request
|
|
155
|
+
- On startup, state is loaded if the timestamp is within the current month
|
|
156
|
+
- State automatically resets at the beginning of each month (matches API quota cycle)
|
|
157
|
+
- If the state file is missing or corrupted, the server starts with default quota (2000 requests per key)
|
|
158
|
+
|
|
159
|
+
**State File Format:**
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"version": 1,
|
|
163
|
+
"keys": {
|
|
164
|
+
"BSKa1234": {
|
|
165
|
+
"remaining": 1500,
|
|
166
|
+
"timestamp": "2025-02-03T10:00:00Z"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Note:** For security, only the first 8 characters of each API key are stored in the state file.
|
|
173
|
+
|
|
132
174
|
### Command Line Options
|
|
133
175
|
|
|
134
176
|
```bash
|
|
@@ -2,17 +2,25 @@
|
|
|
2
2
|
* API Key Manager for multi-key support with quota tracking
|
|
3
3
|
* Designed for free tier keys (2000 requests/month per key)
|
|
4
4
|
*/
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as os from 'node:os';
|
|
5
8
|
// Free tier constants
|
|
6
9
|
const FREE_TIER_MONTHLY_QUOTA = 2000;
|
|
7
10
|
const DEFAULT_PER_SECOND_LIMIT = 1;
|
|
11
|
+
// State file path
|
|
12
|
+
const STATE_FILE = path.join(os.homedir(), '.brave-mcp', 'state.json');
|
|
8
13
|
class ApiKeyManager {
|
|
9
14
|
keys;
|
|
10
15
|
constructor(apiKeys, initialQuota = FREE_TIER_MONTHLY_QUOTA) {
|
|
11
16
|
this.keys = new Map();
|
|
17
|
+
const savedStates = this.loadState();
|
|
12
18
|
for (const key of apiKeys) {
|
|
19
|
+
const keyHash = key.slice(0, 8);
|
|
20
|
+
const savedRemaining = savedStates.get(keyHash);
|
|
13
21
|
this.keys.set(key, {
|
|
14
22
|
key,
|
|
15
|
-
remainingQuota: initialQuota,
|
|
23
|
+
remainingQuota: savedRemaining ?? initialQuota,
|
|
16
24
|
monthLimit: FREE_TIER_MONTHLY_QUOTA,
|
|
17
25
|
remainingSecond: DEFAULT_PER_SECOND_LIMIT,
|
|
18
26
|
secondLimit: DEFAULT_PER_SECOND_LIMIT,
|
|
@@ -21,6 +29,60 @@ class ApiKeyManager {
|
|
|
21
29
|
});
|
|
22
30
|
}
|
|
23
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Load saved state from file, filtering for current month only
|
|
34
|
+
* Returns Map of keyHash -> remaining quota
|
|
35
|
+
*/
|
|
36
|
+
loadState() {
|
|
37
|
+
try {
|
|
38
|
+
if (!fs.existsSync(STATE_FILE)) {
|
|
39
|
+
return new Map();
|
|
40
|
+
}
|
|
41
|
+
const data = JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
|
|
42
|
+
const now = new Date();
|
|
43
|
+
const result = new Map();
|
|
44
|
+
for (const [keyHash, info] of Object.entries(data.keys)) {
|
|
45
|
+
const timestamp = new Date(info.timestamp);
|
|
46
|
+
// Only use state from current month
|
|
47
|
+
if (timestamp.getMonth() === now.getMonth() &&
|
|
48
|
+
timestamp.getFullYear() === now.getFullYear()) {
|
|
49
|
+
result.set(keyHash, info.remaining);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
console.log(`[ApiKeyManager] Loaded state for ${result.size} keys from ${STATE_FILE}`);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.warn(`[ApiKeyManager] Failed to load state: ${err.message}`);
|
|
57
|
+
return new Map();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Save current state to file
|
|
62
|
+
*/
|
|
63
|
+
saveState() {
|
|
64
|
+
try {
|
|
65
|
+
const dir = path.dirname(STATE_FILE);
|
|
66
|
+
if (!fs.existsSync(dir)) {
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
const data = {
|
|
70
|
+
version: 1,
|
|
71
|
+
keys: {},
|
|
72
|
+
};
|
|
73
|
+
for (const [key, state] of this.keys.entries()) {
|
|
74
|
+
const keyHash = key.slice(0, 8);
|
|
75
|
+
data.keys[keyHash] = {
|
|
76
|
+
remaining: state.remainingQuota,
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(data, null, 2));
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.warn(`[ApiKeyManager] Failed to save state: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
24
86
|
/**
|
|
25
87
|
* Select the best available key based on remaining quota
|
|
26
88
|
* Returns null if no valid keys available
|
|
@@ -78,6 +140,8 @@ class ApiKeyManager {
|
|
|
78
140
|
state.monthLimit = limit.perMonth;
|
|
79
141
|
state.secondLimit = limit.perSecond;
|
|
80
142
|
state.inUse = false;
|
|
143
|
+
// Save state after update
|
|
144
|
+
this.saveState();
|
|
81
145
|
console.debug(`[ApiKeyManager] Key ${apiKey.slice(0, 8)}... quota: ${state.remainingQuota}/${state.monthLimit}`);
|
|
82
146
|
}
|
|
83
147
|
/**
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@dyingc/brave-search-mcp-server",
|
|
3
3
|
"mcpName": "io.github.brave/brave-search-mcp-server",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "2.0.
|
|
5
|
+
"version": "2.0.70",
|
|
6
6
|
"description": "Brave Search MCP Server: web results, images, videos, rich results, AI summaries, and more.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"api",
|