bots-garden/mini-mepublic Fork
⑂ fix/tool-order
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. f854e27 · on fix/tool-order · k33g · Sep 23, 2026
mcp-decisions.md · 384 lines · 11.7 KBmarkdown
Blame HistoryOpen raw

MCP Design Decisions

This document explains the key design decisions made when implementing MCP (Model Context Protocol) support in mini-me.

Table of Contents


HTTP-Only Transport

Decision: Only implement HTTP transport (streamable HTTP), not stdio.

Rationale:

  1. Simplicity: HTTP transport is simpler to implement and debug
  2. Common Use Case: Most hosted MCP servers use HTTP transport
  3. Security: HTTP over TLS provides built-in security
  4. Compatibility: Works with all hosted MCP services (GitHub, Context7, Wikipedia, etc.)
  5. Implementation Effort: Stdio requires subprocess management, which adds complexity

Trade-offs:

  • Cannot use local stdio-based MCP servers
  • Cannot use Docker-based MCP servers that only expose stdio

Future: Stdio transport will be added when there's demand for local MCP servers.


Text-Only Support

Decision: Only support MCP tools that use text input/output. Filter out binary tools.

Rationale:

  1. Design Philosophy: Mini-me is designed as a text-based agent
  2. Safety: Binary data can be dangerous (images, executables, etc.)
  3. Simplicity: Text is easier to handle, log, and display
  4. Compatibility: All major MCP servers (GitHub, Context7, Wikipedia) use text-only tools
  5. Context: Mini-me's context window is text-based

Implementation:

// In ConnectAll() after discovering tools
for _, tool := range tools {
    if !tool.IsTextOnly() {
        warnings = append(warnings,
            fmt.Sprintf("mcp server %s: tool %s uses binary I/O - skipped (text-only)",
                server.Name, tool.Name))
        continue
    }
    textTools = append(textTools, tool)
}

Detection: The IsTextOnly() method checks for:

  • Input parameters with type: "string" and format: "binary"
  • Input parameters with type: "binary"
  • Output properties with type: "string" and format: "binary"
  • Output properties with type: "binary"

No Stdio Transport (Yet)

Decision: Defer stdio transport implementation.

Rationale:

  1. HTTP is Sufficient: All currently known public MCP servers use HTTP
  2. Complexity: Stdio requires subprocess management, input/output streaming
  3. Priorities: Focus on HTTP transport first, add stdio later if needed
  4. Use Cases: Stdio is mainly for local/self-hosted MCP servers

Workaround: Use HTTP-based MCP servers or run MCP servers as HTTP services.

Future: Will implement when users request local stdio-based MCP servers.


No OAuth 2.0 Support

Decision: Only support bearer token and API key authentication. No OAuth 2.0 flow.

Rationale:

  1. Complexity: OAuth 2.0 requires browser interaction, token refresh, etc.
  2. Headless: Mini-me often runs in headless environments (CI/CD, scripts)
  3. Current Servers: Major MCP servers (GitHub, Context7) support bearer tokens
  4. Security: Bearer tokens are sufficient for most use cases
  5. Implementation Effort: OAuth 2.0 flow is complex to implement correctly

Trade-offs:

  • Cannot use MCP servers that require OAuth 2.0
  • GitHub MCP server works with PAT (Personal Access Token), not OAuth

Workaround: Use bearer tokens (PAT) instead of OAuth for GitHub.

Future: OAuth 2.0 will be added if a major MCP server requires it.


No Caching

Decision: Don't cache discovered tools between runs.

Rationale:

  1. Freshness: MCP servers may update their tools, caching could cause stale data
  2. Simplicity: No need to manage cache invalidation, storage, etc.
  3. Startup Time: Discovery is fast enough for most use cases
  4. Memory: Caching would use more memory for minimal benefit
  5. Correctness: Always have the latest tool list

Trade-offs:

  • Slightly longer startup time (but discovery is parallel)
  • Cannot use mini-me if MCP servers are offline at startup

Future: May add optional caching if startup time becomes an issue.


No Lazy Discovery

Decision: Discover all MCP tools at startup, not lazily.

Rationale:

  1. User Experience: Users see all available tools immediately
  2. Error Detection: Can detect configuration errors at startup
  3. Simplicity: Easier to implement and reason about
  4. Consistency: All tools available from first use
  5. Validation: Can validate tool schemas at startup

Trade-offs:

  • Longer startup time (mitigated by parallel discovery)
  • All MCP servers must be accessible at startup

Future: May add lazy discovery as an option for slow MCP servers.


Tool Name Prefixing

Decision: Prefix all MCP tool names with their server name.

Rationale:

  1. Avoid Collisions: Different MCP servers might have tools with the same name
  2. Clarity: Users can see which server a tool comes from
  3. Namespacing: Creates a natural namespace for MCP tools
  4. Discoverability: Makes it clear where tools come from

Implementation:

func SanitizeToolName(serverName, toolName string) (string, error) {
    sanitized := sanitizeName(toolName)
    fullName := serverName + "_" + sanitized
    // Check for collision with built-in tools
    if IsBuiltinTool(fullName) {
        return "", fmt.Errorf("tool name %q collides with built-in tool", fullName)
    }
    return fullName, nil
}

Examples:

  • Server: github, Tool: list_repositories → github_list_repositories
  • Server: wikipedia, Tool: search_wikipedia → wikipedia_search_wikipedia
  • Server: context7, Tool: resolve-library-id → context7_resolve_library_id

Name Sanitization:

  • Non-alphanumeric characters → _
  • Leading/trailing _ removed
  • Multiple consecutive _ collapsed to one

Tool Filtering

Decision: Allow users to filter which tools from each MCP server are exposed.

Rationale:

  1. Focus: Users may only need a subset of available tools
  2. Safety: Prevent accidental use of powerful tools
  3. Performance: Reduces tool list size, speeds up startup
  4. Customization: Users can customize their tool set

Implementation:

servers:
  - name: github
    transport: http
    url: https://api.githubcopilot.com/mcp/
    auth:
      type: bearer
      tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN
    tools:  # Only these tools will be available
      - get_me
      - list_repositories
      - search_code

Default: If tools list is empty or not specified, all text-only tools are exposed.


Authentication via Environment Variables

Decision: All authentication tokens/keys are loaded from environment variables.

Rationale:

  1. Security: Prevents accidental commit of secrets to version control
  2. Best Practice: Follows the 12-factor app methodology
  3. Flexibility: Allows different tokens for different environments
  4. Simplicity: Easy to use with existing secret management
  5. Compatibility: Works with Docker, Kubernetes, CI/CD systems

Implementation:

func (c *HTTPClient) addAuthHeaders(req *http.Request) error {
    switch c.server.Auth.Type {
    case "none", "":
        return nil
    case "bearer":
        token := os.Getenv(c.server.Auth.TokenEnv)
        if token == "" {
            return fmt.Errorf("missing bearer token for env var %s", c.server.Auth.TokenEnv)
        }
        req.Header.Set("Authorization", "Bearer "+token)
    case "api_key":
        key := os.Getenv(c.server.Auth.APIKeyEnv)
        if key == "" {
            return fmt.Errorf("missing API key for env var %s", c.server.Auth.APIKeyEnv)
        }
        header := c.server.Auth.APIKeyHeader
        if header == "" {
            header = "Authorization"
        }
        req.Header.Set(header, key)
    default:
        return fmt.Errorf("unknown auth type: %s", c.server.Auth.Type)
    }
    return nil
}

Permission System

Decision: Default to ask permission mode, allowing auto as an option.

Rationale:

  1. Safety First: MCP tools can be powerful (code access, repository changes, etc.)
  2. User Control: Users should explicitly allow MCP tool execution
  3. Flexibility: Allow auto for trusted environments
  4. Consistency: Matches mini-me's existing permission philosophy

Permission Modes:

Mode Behavior Use Case
ask Prompt user before each MCP tool call Production, new users
auto Execute MCP tools automatically Trusted environments, CI/CD

Implementation:

// Global default
mcp:
  enabled: true
  permissions: ask  # Default

// Per-server override
servers:
  - name: wikipedia
    # ...
    permissions: auto  # Override for this server

Error Handling

Decision: Fail gracefully - skip problematic servers/tools with warnings.

Rationale:

  1. Resilience: One bad MCP server shouldn't prevent mini-me from starting
  2. User Awareness: Warnings inform users of issues
  3. Debugging: Clear error messages help diagnose problems
  4. Safety: Prevents cascading failures

Implementation:

  • Server level: If a server fails to connect or authenticate, skip it and log warning
  • Tool level: If a tool fails to parse or is binary-only, skip it and log warning
  • Discovery level: If tool discovery fails, skip the server and log warning
  • Execution level: If tool execution fails, return error to user

SSE Parsing

Decision: Implement custom SSE parsing with large buffer for GitHub MCP server.

Rationale:

  1. GitHub's Large Responses: GitHub MCP server returns tools with base64-encoded icons
  2. Default Limit: Go's bufio.Scanner has a 64KB token limit
  3. GitHub Exceeds: GitHub's response is >64KB, causing "token too long" errors
  4. Solution: Use bufio.Reader with 10MB buffer instead of bufio.Scanner

Implementation:

func (c *HTTPClient) parseSSEResponse(body io.Reader) (*mcpResponse, error) {
    const maxLineSize = 10 * 1024 * 1024  // 10 MB
    reader := bufio.NewReaderSize(body, maxLineSize)
    
    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            if err == io.EOF {
                break
            }
            return nil, fmt.Errorf("failed to read SSE stream: %w", err)
        }
        // Parse line...
    }
    return nil, fmt.Errorf("no valid JSON-RPC response found in SSE stream")
}

Summary

Decision Rationale Future
HTTP-only transport Simplicity, covers all major servers Add stdio later
Text-only support Design philosophy, safety, simplicity Keep text-only
No stdio transport HTTP sufficient, complexity Add if needed
No OAuth 2.0 Complexity, current servers use tokens Add if required
No caching Freshness, simplicity Add if needed
No lazy discovery UX, error detection, simplicity Add as option
Tool name prefixing Avoid collisions, clarity Keep
Tool filtering Focus, safety, performance Keep
Env var auth Security, best practice Keep
Permission system Safety first, flexibility Keep
Graceful errors Resilience, user awareness Keep
Custom SSE parsing Handle large responses Keep

See Also

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# MCP Design Decisions

This document explains the key design decisions made when implementing MCP (Model Context Protocol) support in mini-me.

## Table of Contents

- [HTTP-Only Transport](#http-only-transport)
- [Text-Only Support](#text-only-support)
- [No Stdio Transport (Yet)](#no-stdio-transport-yet)
- [No OAuth 2.0 Support](#no-oauth-20-support)
- [No Caching](#no-caching)
- [No Lazy Discovery](#no-lazy-discovery)
- [Tool Name Prefixing](#tool-name-prefixing)
- [Tool Filtering](#tool-filtering)
- [Authentication via Environment Variables](#authentication-via-environment-variables)
- [Permission System](#permission-system)
- [Error Handling](#error-handling)
- [SSE Parsing](#sse-parsing)

---

## HTTP-Only Transport

**Decision:** Only implement HTTP transport (streamable HTTP), not stdio.

**Rationale:**

1. **Simplicity:** HTTP transport is simpler to implement and debug
2. **Common Use Case:** Most hosted MCP servers use HTTP transport
3. **Security:** HTTP over TLS provides built-in security
4. **Compatibility:** Works with all hosted MCP services (GitHub, Context7, Wikipedia, etc.)
5. **Implementation Effort:** Stdio requires subprocess management, which adds complexity

**Trade-offs:**

- Cannot use local stdio-based MCP servers
- Cannot use Docker-based MCP servers that only expose stdio

**Future:** Stdio transport will be added when there's demand for local MCP servers.

---

## Text-Only Support

**Decision:** Only support MCP tools that use text input/output. Filter out binary tools.

**Rationale:**

1. **Design Philosophy:** Mini-me is designed as a text-based agent
2. **Safety:** Binary data can be dangerous (images, executables, etc.)
3. **Simplicity:** Text is easier to handle, log, and display
4. **Compatibility:** All major MCP servers (GitHub, Context7, Wikipedia) use text-only tools
5. **Context:** Mini-me's context window is text-based

**Implementation:**

```go
// In ConnectAll() after discovering tools
for _, tool := range tools {
    if !tool.IsTextOnly() {
        warnings = append(warnings,
            fmt.Sprintf("mcp server %s: tool %s uses binary I/O - skipped (text-only)",
                server.Name, tool.Name))
        continue
    }
    textTools = append(textTools, tool)
}
```

**Detection:** The `IsTextOnly()` method checks for:
- Input parameters with `type: "string"` and `format: "binary"`
- Input parameters with `type: "binary"`
- Output properties with `type: "string"` and `format: "binary"`
- Output properties with `type: "binary"`

---

## No Stdio Transport (Yet)

**Decision:** Defer stdio transport implementation.

**Rationale:**

1. **HTTP is Sufficient:** All currently known public MCP servers use HTTP
2. **Complexity:** Stdio requires subprocess management, input/output streaming
3. **Priorities:** Focus on HTTP transport first, add stdio later if needed
4. **Use Cases:** Stdio is mainly for local/self-hosted MCP servers

**Workaround:** Use HTTP-based MCP servers or run MCP servers as HTTP services.

**Future:** Will implement when users request local stdio-based MCP servers.

---

## No OAuth 2.0 Support

**Decision:** Only support bearer token and API key authentication. No OAuth 2.0 flow.

**Rationale:**

1. **Complexity:** OAuth 2.0 requires browser interaction, token refresh, etc.
2. **Headless:** Mini-me often runs in headless environments (CI/CD, scripts)
3. **Current Servers:** Major MCP servers (GitHub, Context7) support bearer tokens
4. **Security:** Bearer tokens are sufficient for most use cases
5. **Implementation Effort:** OAuth 2.0 flow is complex to implement correctly

**Trade-offs:**

- Cannot use MCP servers that require OAuth 2.0
- GitHub MCP server works with PAT (Personal Access Token), not OAuth

**Workaround:** Use bearer tokens (PAT) instead of OAuth for GitHub.

**Future:** OAuth 2.0 will be added if a major MCP server requires it.

---

## No Caching

**Decision:** Don't cache discovered tools between runs.

**Rationale:**

1. **Freshness:** MCP servers may update their tools, caching could cause stale data
2. **Simplicity:** No need to manage cache invalidation, storage, etc.
3. **Startup Time:** Discovery is fast enough for most use cases
4. **Memory:** Caching would use more memory for minimal benefit
5. **Correctness:** Always have the latest tool list

**Trade-offs:**

- Slightly longer startup time (but discovery is parallel)
- Cannot use mini-me if MCP servers are offline at startup

**Future:** May add optional caching if startup time becomes an issue.

---

## No Lazy Discovery

**Decision:** Discover all MCP tools at startup, not lazily.

**Rationale:**

1. **User Experience:** Users see all available tools immediately
2. **Error Detection:** Can detect configuration errors at startup
3. **Simplicity:** Easier to implement and reason about
4. **Consistency:** All tools available from first use
5. **Validation:** Can validate tool schemas at startup

**Trade-offs:**

- Longer startup time (mitigated by parallel discovery)
- All MCP servers must be accessible at startup

**Future:** May add lazy discovery as an option for slow MCP servers.

---

## Tool Name Prefixing

**Decision:** Prefix all MCP tool names with their server name.

**Rationale:**

1. **Avoid Collisions:** Different MCP servers might have tools with the same name
2. **Clarity:** Users can see which server a tool comes from
3. **Namespacing:** Creates a natural namespace for MCP tools
4. **Discoverability:** Makes it clear where tools come from

**Implementation:**

```go
func SanitizeToolName(serverName, toolName string) (string, error) {
    sanitized := sanitizeName(toolName)
    fullName := serverName + "_" + sanitized
    // Check for collision with built-in tools
    if IsBuiltinTool(fullName) {
        return "", fmt.Errorf("tool name %q collides with built-in tool", fullName)
    }
    return fullName, nil
}
```

**Examples:**
- Server: `github`, Tool: `list_repositories` → `github_list_repositories`
- Server: `wikipedia`, Tool: `search_wikipedia` → `wikipedia_search_wikipedia`
- Server: `context7`, Tool: `resolve-library-id` → `context7_resolve_library_id`

**Name Sanitization:**
- Non-alphanumeric characters → `_`
- Leading/trailing `_` removed
- Multiple consecutive `_` collapsed to one

---

## Tool Filtering

**Decision:** Allow users to filter which tools from each MCP server are exposed.

**Rationale:**

1. **Focus:** Users may only need a subset of available tools
2. **Safety:** Prevent accidental use of powerful tools
3. **Performance:** Reduces tool list size, speeds up startup
4. **Customization:** Users can customize their tool set

**Implementation:**

```yaml
servers:
  - name: github
    transport: http
    url: https://api.githubcopilot.com/mcp/
    auth:
      type: bearer
      tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN
    tools:  # Only these tools will be available
      - get_me
      - list_repositories
      - search_code
```

**Default:** If `tools` list is empty or not specified, all text-only tools are exposed.

---

## Authentication via Environment Variables

**Decision:** All authentication tokens/keys are loaded from environment variables.

**Rationale:**

1. **Security:** Prevents accidental commit of secrets to version control
2. **Best Practice:** Follows the 12-factor app methodology
3. **Flexibility:** Allows different tokens for different environments
4. **Simplicity:** Easy to use with existing secret management
5. **Compatibility:** Works with Docker, Kubernetes, CI/CD systems

**Implementation:**

```go
func (c *HTTPClient) addAuthHeaders(req *http.Request) error {
    switch c.server.Auth.Type {
    case "none", "":
        return nil
    case "bearer":
        token := os.Getenv(c.server.Auth.TokenEnv)
        if token == "" {
            return fmt.Errorf("missing bearer token for env var %s", c.server.Auth.TokenEnv)
        }
        req.Header.Set("Authorization", "Bearer "+token)
    case "api_key":
        key := os.Getenv(c.server.Auth.APIKeyEnv)
        if key == "" {
            return fmt.Errorf("missing API key for env var %s", c.server.Auth.APIKeyEnv)
        }
        header := c.server.Auth.APIKeyHeader
        if header == "" {
            header = "Authorization"
        }
        req.Header.Set(header, key)
    default:
        return fmt.Errorf("unknown auth type: %s", c.server.Auth.Type)
    }
    return nil
}
```

---

## Permission System

**Decision:** Default to `ask` permission mode, allowing `auto` as an option.

**Rationale:**

1. **Safety First:** MCP tools can be powerful (code access, repository changes, etc.)
2. **User Control:** Users should explicitly allow MCP tool execution
3. **Flexibility:** Allow `auto` for trusted environments
4. **Consistency:** Matches mini-me's existing permission philosophy

**Permission Modes:**

| Mode | Behavior | Use Case |
|------|----------|----------|
| `ask` | Prompt user before each MCP tool call | Production, new users |
| `auto` | Execute MCP tools automatically | Trusted environments, CI/CD |

**Implementation:**

```go
// Global default
mcp:
  enabled: true
  permissions: ask  # Default

// Per-server override
servers:
  - name: wikipedia
    # ...
    permissions: auto  # Override for this server
```

---

## Error Handling

**Decision:** Fail gracefully - skip problematic servers/tools with warnings.

**Rationale:**

1. **Resilience:** One bad MCP server shouldn't prevent mini-me from starting
2. **User Awareness:** Warnings inform users of issues
3. **Debugging:** Clear error messages help diagnose problems
4. **Safety:** Prevents cascading failures

**Implementation:**

- **Server level:** If a server fails to connect or authenticate, skip it and log warning
- **Tool level:** If a tool fails to parse or is binary-only, skip it and log warning
- **Discovery level:** If tool discovery fails, skip the server and log warning
- **Execution level:** If tool execution fails, return error to user

---

## SSE Parsing

**Decision:** Implement custom SSE parsing with large buffer for GitHub MCP server.

**Rationale:**

1. **GitHub's Large Responses:** GitHub MCP server returns tools with base64-encoded icons
2. **Default Limit:** Go's `bufio.Scanner` has a 64KB token limit
3. **GitHub Exceeds:** GitHub's response is >64KB, causing "token too long" errors
4. **Solution:** Use `bufio.Reader` with 10MB buffer instead of `bufio.Scanner`

**Implementation:**

```go
func (c *HTTPClient) parseSSEResponse(body io.Reader) (*mcpResponse, error) {
    const maxLineSize = 10 * 1024 * 1024  // 10 MB
    reader := bufio.NewReaderSize(body, maxLineSize)
    
    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            if err == io.EOF {
                break
            }
            return nil, fmt.Errorf("failed to read SSE stream: %w", err)
        }
        // Parse line...
    }
    return nil, fmt.Errorf("no valid JSON-RPC response found in SSE stream")
}
```

---

## Summary

| Decision | Rationale | Future |
|----------|-----------|--------|
| HTTP-only transport | Simplicity, covers all major servers | Add stdio later |
| Text-only support | Design philosophy, safety, simplicity | Keep text-only |
| No stdio transport | HTTP sufficient, complexity | Add if needed |
| No OAuth 2.0 | Complexity, current servers use tokens | Add if required |
| No caching | Freshness, simplicity | Add if needed |
| No lazy discovery | UX, error detection, simplicity | Add as option |
| Tool name prefixing | Avoid collisions, clarity | Keep |
| Tool filtering | Focus, safety, performance | Keep |
| Env var auth | Security, best practice | Keep |
| Permission system | Safety first, flexibility | Keep |
| Graceful errors | Resilience, user awareness | Keep |
| Custom SSE parsing | Handle large responses | Keep |

---

## See Also

- [MCP Configuration Reference](configuration.md)
- [Available MCP Tools](available-tools.md)
- [MCP Architecture](architecture.md)