Skip to content

Commit 4751083

Browse files
authored
Make rate limit constants configurable via environment variables (#139)
Add support for configuring rate limits via environment variables: - MKP_RATE_LIMIT_DEFAULT: default rate limit (default: 60) - MKP_RATE_LIMIT_READ: read operations limit (default: 120) - MKP_RATE_LIMIT_WRITE: write operations limit (default: 30) This resolves the TODO comment in pkg/ratelimit/config.go and allows operators to tune rate limits based on their deployment needs. Signed-off-by: majiayu000 <1835304752@qq.com>
1 parent e2fbd81 commit 4751083

File tree

4 files changed

+138
-27
lines changed

4 files changed

+138
-27
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,17 @@ via the command line flag:
536536
./build/mkp-server --enable-rate-limiting=false
537537
```
538538

539+
Rate limits can be customized via environment variables:
540+
541+
- `MKP_RATE_LIMIT_DEFAULT`: Default rate limit (default: 60)
542+
- `MKP_RATE_LIMIT_READ`: Read operations rate limit (default: 120)
543+
- `MKP_RATE_LIMIT_WRITE`: Write operations rate limit (default: 30)
544+
545+
```bash
546+
# Run with custom rate limits
547+
MKP_RATE_LIMIT_READ=200 MKP_RATE_LIMIT_WRITE=50 ./build/mkp-server
548+
```
549+
539550
## Development
540551

541552
### Running tests

pkg/ratelimit/config.go

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,74 @@
11
// Package ratelimit provides rate limiting functionality for the MCP server
22
package ratelimit
33

4-
import "github.com/StacklokLabs/mkp/pkg/types"
4+
import (
5+
"os"
6+
"strconv"
7+
8+
"github.com/StacklokLabs/mkp/pkg/types"
9+
)
510

6-
// TODO: make these constants configurable
711
const (
8-
defaultLimit = 60
9-
readLimit = 120 // 120 requests per minute (2 per second)
10-
writeLimit = 30 // 30 requests per minute (0.5 per second)
11-
DefaultTool = "default"
12+
// DefaultTool is the key for the default rate limit
13+
DefaultTool = "default"
14+
15+
// Default values for rate limits (requests per minute)
16+
defaultDefaultLimit = 60
17+
defaultReadLimit = 120 // 120 requests per minute (2 per second)
18+
defaultWriteLimit = 30 // 30 requests per minute (0.5 per second)
1219
)
1320

14-
// DefaultConfig defines the default rate limits for different tools
15-
var DefaultConfig = map[string]int{
16-
// Read operations - higher limits
17-
types.ListResourcesToolName: readLimit,
18-
types.GetResourceToolName: readLimit,
21+
// Config holds the rate limiting configuration
22+
type Config struct {
23+
// DefaultLimit is the default rate limit for tools not explicitly configured
24+
DefaultLimit int
25+
// ReadLimit is the rate limit for read operations (list_resources, get_resource)
26+
ReadLimit int
27+
// WriteLimit is the rate limit for write operations (apply_resource, delete_resource)
28+
WriteLimit int
29+
}
1930

20-
// Write operations - lower limits
21-
types.ApplyResourceToolName: writeLimit,
22-
types.DeleteResourceToolName: writeLimit,
31+
// NewDefaultConfig returns a Config with default values, optionally overridden by environment variables:
32+
// - MKP_RATE_LIMIT_DEFAULT: default rate limit (default: 60)
33+
// - MKP_RATE_LIMIT_READ: read operations rate limit (default: 120)
34+
// - MKP_RATE_LIMIT_WRITE: write operations rate limit (default: 30)
35+
func NewDefaultConfig() *Config {
36+
return &Config{
37+
DefaultLimit: getEnvInt("MKP_RATE_LIMIT_DEFAULT", defaultDefaultLimit),
38+
ReadLimit: getEnvInt("MKP_RATE_LIMIT_READ", defaultReadLimit),
39+
WriteLimit: getEnvInt("MKP_RATE_LIMIT_WRITE", defaultWriteLimit),
40+
}
41+
}
2342

24-
// Default for any other tool
25-
DefaultTool: defaultLimit,
43+
// getEnvInt returns the integer value of an environment variable or a default value
44+
func getEnvInt(key string, defaultValue int) int {
45+
if value := os.Getenv(key); value != "" {
46+
if intValue, err := strconv.Atoi(value); err == nil && intValue > 0 {
47+
return intValue
48+
}
49+
}
50+
return defaultValue
2651
}
2752

2853
// GetDefaultRateLimiter returns a RateLimiter with default configuration
2954
func GetDefaultRateLimiter() *RateLimiter {
30-
options := []RateLimiterOption{
31-
WithDefaultLimit(DefaultConfig[DefaultTool]),
55+
return GetRateLimiterWithConfig(nil)
56+
}
57+
58+
// GetRateLimiterWithConfig returns a RateLimiter with the given configuration.
59+
// If config is nil, default configuration is used.
60+
func GetRateLimiterWithConfig(config *Config) *RateLimiter {
61+
if config == nil {
62+
config = NewDefaultConfig()
3263
}
3364

34-
// Add tool-specific limits
35-
for tool, limit := range DefaultConfig {
36-
if tool != DefaultTool {
37-
options = append(options, WithToolLimit(tool, limit))
38-
}
65+
options := []RateLimiterOption{
66+
WithDefaultLimit(config.DefaultLimit),
67+
WithToolLimit(types.ListResourcesToolName, config.ReadLimit),
68+
WithToolLimit(types.GetResourceToolName, config.ReadLimit),
69+
WithToolLimit(types.ApplyResourceToolName, config.WriteLimit),
70+
WithToolLimit(types.DeleteResourceToolName, config.WriteLimit),
3971
}
4072

41-
limiter := NewRateLimiter(options...)
42-
return limiter
73+
return NewRateLimiter(options...)
4374
}

pkg/ratelimit/ratelimit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func WithTimeWindow(window time.Duration) RateLimiterOption {
6060
func NewRateLimiter(opts ...RateLimiterOption) *RateLimiter {
6161
rl := &RateLimiter{
6262
limits: make(map[string]int),
63-
defaultLimit: defaultLimit,
63+
defaultLimit: defaultDefaultLimit,
6464
requestCounts: make(map[string]map[string]*windowCounter),
6565
}
6666

pkg/ratelimit/ratelimit_test.go

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ratelimit
22

33
import (
44
"context"
5+
"os"
56
"testing"
67
"time"
78

@@ -14,7 +15,7 @@ func TestRateLimiterCreation(t *testing.T) {
1415
// Test with default options
1516
limiter := NewRateLimiter()
1617
assert.NotNil(t, limiter)
17-
assert.Equal(t, defaultLimit, limiter.defaultLimit)
18+
assert.Equal(t, defaultDefaultLimit, limiter.defaultLimit)
1819

1920
// Test with custom options
2021
customLimiter := NewRateLimiter(
@@ -258,3 +259,71 @@ func TestToolSpecificLimits(t *testing.T) {
258259
assert.True(t, result.IsError)
259260
assert.Contains(t, result.Content[0].(mcp.TextContent).Text, "Rate limit exceeded")
260261
}
262+
263+
func TestNewDefaultConfig(t *testing.T) {
264+
// Test default values
265+
config := NewDefaultConfig()
266+
assert.Equal(t, defaultDefaultLimit, config.DefaultLimit)
267+
assert.Equal(t, defaultReadLimit, config.ReadLimit)
268+
assert.Equal(t, defaultWriteLimit, config.WriteLimit)
269+
}
270+
271+
func TestConfigFromEnv(t *testing.T) {
272+
// Save original env values and restore after test
273+
origDefault := os.Getenv("MKP_RATE_LIMIT_DEFAULT")
274+
origRead := os.Getenv("MKP_RATE_LIMIT_READ")
275+
origWrite := os.Getenv("MKP_RATE_LIMIT_WRITE")
276+
defer func() {
277+
os.Setenv("MKP_RATE_LIMIT_DEFAULT", origDefault)
278+
os.Setenv("MKP_RATE_LIMIT_READ", origRead)
279+
os.Setenv("MKP_RATE_LIMIT_WRITE", origWrite)
280+
}()
281+
282+
// Set custom env values
283+
os.Setenv("MKP_RATE_LIMIT_DEFAULT", "100")
284+
os.Setenv("MKP_RATE_LIMIT_READ", "200")
285+
os.Setenv("MKP_RATE_LIMIT_WRITE", "50")
286+
287+
config := NewDefaultConfig()
288+
assert.Equal(t, 100, config.DefaultLimit)
289+
assert.Equal(t, 200, config.ReadLimit)
290+
assert.Equal(t, 50, config.WriteLimit)
291+
}
292+
293+
func TestConfigFromEnvInvalidValues(t *testing.T) {
294+
// Save original env values and restore after test
295+
origDefault := os.Getenv("MKP_RATE_LIMIT_DEFAULT")
296+
defer os.Setenv("MKP_RATE_LIMIT_DEFAULT", origDefault)
297+
298+
// Test with invalid value (non-numeric)
299+
os.Setenv("MKP_RATE_LIMIT_DEFAULT", "invalid")
300+
config := NewDefaultConfig()
301+
assert.Equal(t, defaultDefaultLimit, config.DefaultLimit)
302+
303+
// Test with invalid value (zero)
304+
os.Setenv("MKP_RATE_LIMIT_DEFAULT", "0")
305+
config = NewDefaultConfig()
306+
assert.Equal(t, defaultDefaultLimit, config.DefaultLimit)
307+
308+
// Test with invalid value (negative)
309+
os.Setenv("MKP_RATE_LIMIT_DEFAULT", "-10")
310+
config = NewDefaultConfig()
311+
assert.Equal(t, defaultDefaultLimit, config.DefaultLimit)
312+
}
313+
314+
func TestGetRateLimiterWithConfig(t *testing.T) {
315+
// Test with nil config (should use defaults)
316+
limiter := GetRateLimiterWithConfig(nil)
317+
assert.NotNil(t, limiter)
318+
assert.Equal(t, defaultDefaultLimit, limiter.defaultLimit)
319+
320+
// Test with custom config
321+
config := &Config{
322+
DefaultLimit: 100,
323+
ReadLimit: 200,
324+
WriteLimit: 50,
325+
}
326+
limiter = GetRateLimiterWithConfig(config)
327+
assert.NotNil(t, limiter)
328+
assert.Equal(t, 100, limiter.defaultLimit)
329+
}

0 commit comments

Comments
 (0)