@dyingc/brave-search-mcp-server 2.1.0 → 2.1.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/config.js +17 -0
- package/dist/index.js +1 -0
- package/dist/utils/apiKeyManager.js +34 -19
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -49,6 +49,13 @@ export const configSchema = z.object({
|
|
|
49
49
|
.default(1.0)
|
|
50
50
|
.describe('Temperature for soft-max strategy (0-5.0, 0 = hard limit)')
|
|
51
51
|
.optional(),
|
|
52
|
+
apiKeyExplorationRate: z
|
|
53
|
+
.number()
|
|
54
|
+
.min(0)
|
|
55
|
+
.max(1.0)
|
|
56
|
+
.default(0.1)
|
|
57
|
+
.describe('Epsilon for epsilon-greedy exploration (0-1, 0 = no exploration, 1 = always random)')
|
|
58
|
+
.optional(),
|
|
52
59
|
});
|
|
53
60
|
const state = {
|
|
54
61
|
transport: 'stdio',
|
|
@@ -65,6 +72,7 @@ const state = {
|
|
|
65
72
|
retryMaxDelay: 30000,
|
|
66
73
|
apiKeySelectionStrategy: 'soft-max',
|
|
67
74
|
apiKeyTemperature: 1.0,
|
|
75
|
+
apiKeyExplorationRate: 0.1,
|
|
68
76
|
};
|
|
69
77
|
export function isToolPermittedByUser(toolName) {
|
|
70
78
|
return state.enabledTools.length > 0
|
|
@@ -117,6 +125,7 @@ export function getOptions() {
|
|
|
117
125
|
.option('--retry-max-delay <number>', 'Maximum delay for retry in milliseconds', process.env.BRAVE_MCP_RETRY_MAX_DELAY ?? '30000')
|
|
118
126
|
.option('--api-key-selection-strategy <strategy>', 'API key selection strategy (greedy-max, greedy-min, soft-max)', process.env.BRAVE_API_KEY_SELECTION_STRATEGY ?? 'soft-max')
|
|
119
127
|
.option('--api-key-temperature <number>', 'Temperature for soft-max strategy (0-5.0, default: 1.0, 0 = hard limit)', process.env.BRAVE_API_KEY_TEMPERATURE ?? '1.0')
|
|
128
|
+
.option('--api-key-exploration-rate <number>', 'Epsilon for epsilon-greedy exploration (0-1, default: 0.1)', process.env.BRAVE_API_KEY_EXPLORATION_RATE ?? '0.1')
|
|
120
129
|
.allowUnknownOption()
|
|
121
130
|
.parse(process.argv);
|
|
122
131
|
const options = program.opts();
|
|
@@ -180,6 +189,13 @@ export function getOptions() {
|
|
|
180
189
|
}
|
|
181
190
|
options.apiKeyTemperature = temperature;
|
|
182
191
|
options.apiKeySelectionStrategy = options.apiKeySelectionStrategy;
|
|
192
|
+
// Validate exploration rate (range 0-1)
|
|
193
|
+
const explorationRate = parseFloat(options.apiKeyExplorationRate ?? '0.1');
|
|
194
|
+
if (isNaN(explorationRate) || explorationRate < 0 || explorationRate > 1) {
|
|
195
|
+
console.error(`Invalid --api-key-exploration-rate value: '${options.apiKeyExplorationRate}'. Must be a number between 0 and 1`);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
options.apiKeyExplorationRate = explorationRate;
|
|
183
199
|
if (options.transport === 'http') {
|
|
184
200
|
if (options.port < 1 || options.port > 65535) {
|
|
185
201
|
console.error(`Invalid --port value: '${options.port}'. Must be a valid port number between 1 and 65535.`);
|
|
@@ -206,6 +222,7 @@ export function getOptions() {
|
|
|
206
222
|
state.retryMaxDelay = options.retryMaxDelay;
|
|
207
223
|
state.apiKeySelectionStrategy = options.apiKeySelectionStrategy;
|
|
208
224
|
state.apiKeyTemperature = options.apiKeyTemperature;
|
|
225
|
+
state.apiKeyExplorationRate = options.apiKeyExplorationRate;
|
|
209
226
|
state.ready = true;
|
|
210
227
|
return options;
|
|
211
228
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ async function main() {
|
|
|
12
12
|
initApiKeyManager(options.braveApiKeys, {
|
|
13
13
|
selectionStrategy: options.apiKeySelectionStrategy,
|
|
14
14
|
temperature: options.apiKeyTemperature,
|
|
15
|
+
explorationRate: options.apiKeyExplorationRate,
|
|
15
16
|
});
|
|
16
17
|
// default to stdio server unless http is explicitly requested
|
|
17
18
|
if (options.transport === 'http') {
|
|
@@ -14,12 +14,15 @@ class ApiKeyManager {
|
|
|
14
14
|
keys;
|
|
15
15
|
selectionStrategy;
|
|
16
16
|
temperature;
|
|
17
|
+
explorationRate; // Probability of random exploration
|
|
17
18
|
constructor(apiKeys, options = {}) {
|
|
18
19
|
this.keys = new Map();
|
|
19
20
|
const savedStates = this.loadState();
|
|
20
|
-
const { initialQuota = FREE_TIER_MONTHLY_QUOTA, selectionStrategy = 'soft-max', temperature = 1.0,
|
|
21
|
+
const { initialQuota = FREE_TIER_MONTHLY_QUOTA, selectionStrategy = 'soft-max', temperature = 1.0, explorationRate = 0.1, // Default 10% exploration rate
|
|
22
|
+
} = options;
|
|
21
23
|
this.selectionStrategy = selectionStrategy;
|
|
22
24
|
this.temperature = temperature;
|
|
25
|
+
this.explorationRate = explorationRate;
|
|
23
26
|
for (const key of apiKeys) {
|
|
24
27
|
const keyHash = key.slice(0, 8);
|
|
25
28
|
const savedRemaining = savedStates.get(keyHash);
|
|
@@ -89,6 +92,8 @@ class ApiKeyManager {
|
|
|
89
92
|
}
|
|
90
93
|
/**
|
|
91
94
|
* Select the best available key based on remaining quota
|
|
95
|
+
* Uses epsilon-greedy strategy: explore randomly with probability epsilon,
|
|
96
|
+
* otherwise exploit using the configured selection strategy
|
|
92
97
|
* Returns null if no valid keys available
|
|
93
98
|
*/
|
|
94
99
|
selectBestKey() {
|
|
@@ -104,17 +109,23 @@ class ApiKeyManager {
|
|
|
104
109
|
return availableKeys[0].key;
|
|
105
110
|
}
|
|
106
111
|
let selected;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
// Epsilon-greedy exploration: random selection with probability epsilon
|
|
113
|
+
if (Math.random() < this.explorationRate) {
|
|
114
|
+
selected = availableKeys[Math.floor(Math.random() * availableKeys.length)];
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
switch (this.selectionStrategy) {
|
|
118
|
+
case 'greedy-min':
|
|
119
|
+
selected = this.selectGreedyMin(availableKeys);
|
|
120
|
+
break;
|
|
121
|
+
case 'soft-max':
|
|
122
|
+
selected = this.selectKeyWithSoftMax(availableKeys, this.temperature);
|
|
123
|
+
break;
|
|
124
|
+
case 'greedy-max':
|
|
125
|
+
default:
|
|
126
|
+
selected = this.selectGreedyMax(availableKeys);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
118
129
|
}
|
|
119
130
|
// selected should never be null here since we checked availableKeys.length >= 2
|
|
120
131
|
if (!selected) {
|
|
@@ -145,15 +156,19 @@ class ApiKeyManager {
|
|
|
145
156
|
}
|
|
146
157
|
/**
|
|
147
158
|
* Calculate soft max probabilities for key selection
|
|
148
|
-
* Uses
|
|
159
|
+
* Uses standard soft max formula: softmax_i(z;T) = exp(z_i/T) / sum(exp(z_j/T))
|
|
160
|
+
* where z_i = -remainingQuota (lower remaining = higher selection probability)
|
|
161
|
+
*
|
|
162
|
+
* Example with T=1:
|
|
163
|
+
* - Key with 1 remaining: score = -1, exp(-1) ≈ 0.368
|
|
164
|
+
* - Key with 10 remaining: score = -10, exp(-10) ≈ 0.000045
|
|
165
|
+
* - Key with 1 remaining has much higher selection probability
|
|
166
|
+
*
|
|
167
|
+
* Probabilities always sum to 1 by definition of softmax formula
|
|
149
168
|
*/
|
|
150
169
|
calculateSoftMaxProbabilities(keys, temperature) {
|
|
151
|
-
//
|
|
152
|
-
const
|
|
153
|
-
// Normalize by total (sum of normalized values = 1), then invert (lower quota = higher score)
|
|
154
|
-
// normalized_quota = quota / total_quota ∈ [0, 1]
|
|
155
|
-
// score = -normalized_quota ∈ [-1, 0], lower quota gives score closer to 0
|
|
156
|
-
const scores = keys.map((k) => -k.remainingQuota / totalQuota);
|
|
170
|
+
// Use negative remaining quota as score: lower remaining = higher score
|
|
171
|
+
const scores = keys.map((k) => -k.remainingQuota);
|
|
157
172
|
// Standard soft max: scale by temperature
|
|
158
173
|
const scaledScores = scores.map((s) => s / temperature);
|
|
159
174
|
// Numerical stability: subtract max value
|
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.1.
|
|
5
|
+
"version": "2.1.1",
|
|
6
6
|
"description": "Brave Search MCP Server: web results, images, videos, rich results, AI summaries, and more.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"api",
|