🛟 Updated.
f854e27 parent: cc91f31 modified
.tickets/issues/0004-do-we-need-mcp.yaml +222 -2 | @@ -6,7 +6,7 @@ author: | ||
| 6 | 6 | name: k33g |
| 7 | 7 | email: ph.charriere@gmail.com |
| 8 | 8 | createdAt: 2026-09-15T22:38:45.129Z |
| 9 | -updatedAt: 2026-09-22T20:57:39.912Z | |
| 9 | +updatedAt: 2026-09-23T23:30:00.000Z | |
| 10 | 10 | labels: [] |
| 11 | 11 | body: | |
| 12 | 12 | I would say no - use a CLI |
| @@ -88,6 +88,135 @@ body: | | ||
| 88 | 88 | Tickets 32 (`--prompt`) and 33 (invoke another mini-me) are mm *becoming* the CLI — the same bet as this ticket. For mm being **invoked**, the protocol question is ACP, not MCP-client, which is the instinct already written in 33. |
| 89 | 89 | |
| 90 | 90 | `plugins/mcp` also has a `server.go`: **mm exposing its own tools over MCP** is a different and cheaper question, and deserves its own ticket rather than being folded into this one. |
| 91 | + | |
| 92 | + --- | |
| 93 | + | |
| 94 | + ## Proposal: MCP Client as a Disabled-by-Default Builtin Tool (Vibe, 2026-09-23) | |
| 95 | + | |
| 96 | + ### Design Goals | |
| 97 | + | |
| 98 | + 1. **Disabled by default** — `mcpEnabled: false` in config, consistent with the existing pattern (`bashTool`, `editTools`) | |
| 99 | + 2. **Respect ALL existing contracts** — permissions, truncation, loop detection, error-as-text | |
| 100 | + 3. **Zero impact when disabled** — no discovery, no network calls, no dependency on `mcp-go` | |
| 101 | + 4. **Support both transport types** — stdio (process) and streamable HTTP | |
| 102 | + 5. **Handle authentication** — API keys, Bearer tokens, OAuth2 client credentials | |
| 103 | + 6. **Name collision protection** — prefix with server alias, reject duplicates | |
| 104 | + 7. **Graceful degradation** — server down = skip with warning, never crash | |
| 105 | + | |
| 106 | + ### Architecture Overview | |
| 107 | + | |
| 108 | + ``` | |
| 109 | + internal/mcp/ # New package | |
| 110 | + client.go # MCP client wrapper (stdio + HTTP) | |
| 111 | + config.go # MCPServer config + validation | |
| 112 | + discovery.go # Tool discovery + prefixing | |
| 113 | + | |
| 114 | + internal/tools/mcp.go # The mcp tool factory | |
| 115 | + | |
| 116 | + internal/config/config.go # Add MCPConfig | |
| 117 | + main.go # assembleMCP() alongside assembleLSP() | |
| 118 | + ``` | |
| 119 | + | |
| 120 | + ### Configuration Schema | |
| 121 | + | |
| 122 | + ```yaml | |
| 123 | + mcp: | |
| 124 | + enabled: false # Disabled by default | |
| 125 | + permissions: ask # Per-server override possible; default = ask | |
| 126 | + discoveryTimeout: 5s # Max time to connect and list tools at startup | |
| 127 | + servers: | |
| 128 | + - name: context7 # Server alias (used for naming prefix) | |
| 129 | + url: https://mcp.context7.com/mcp | |
| 130 | + transport: http # "stdio" or "http" | |
| 131 | + auth: | |
| 132 | + type: bearer | |
| 133 | + tokenEnv: CONTEXT7_TOKEN | |
| 134 | + - name: local-git | |
| 135 | + command: /usr/local/bin/mcp-git | |
| 136 | + transport: stdio | |
| 137 | + args: ["--stdio"] | |
| 138 | + ``` | |
| 139 | + | |
| 140 | + **Key design decisions:** | |
| 141 | + - `name` is the server alias, used to prefix all its tools (e.g., `context7_list_resources`) | |
| 142 | + - `url` for HTTP, `command` for stdio — mutually exclusive | |
| 143 | + - Auth config supports: none, bearer, api_key, oauth2 | |
| 144 | + - Timeout at discovery prevents hanging the agent | |
| 145 | + | |
| 146 | + ### Tool Wrapping Strategy | |
| 147 | + | |
| 148 | + Each discovered MCP tool is wrapped in a `genkit.DefineTool` following the `lspEvented` skeleton: | |
| 149 | + sink branch → ToolStart → Allow → ToolRunning → run → ToolEnd → `record(d, name, title, truncate(out))`. | |
| 150 | + | |
| 151 | + **Contract compliance:** | |
| 152 | + - Allow() called via event system (kind="execute" by default) | |
| 153 | + - record() for loop detection | |
| 154 | + - truncate() on output | |
| 155 | + - Errors returned as TEXT | |
| 156 | + - echoOutput() for terminal visibility | |
| 157 | + | |
| 158 | + **Name collision handling:** | |
| 159 | + - Prefix each MCP tool with server alias: `{serverName}_{originalToolName}` | |
| 160 | + - Reject any tool colliding with built-in tools (bash, read_file, write_file, edit_file, lsp, read_skill, plan) | |
| 161 | + - Warn on collisions between MCP servers | |
| 162 | + | |
| 163 | + ### Discovery at Startup | |
| 164 | + | |
| 165 | + Runs in `assembleMCP()` alongside `assembleLSP()` BEFORE banner display: | |
| 166 | + - Configurable timeout (default 5s) | |
| 167 | + - On failure: logs warning, continues without MCP tools | |
| 168 | + - Validates no name collisions BEFORE declaring tools | |
| 169 | + - Static tool list for the session (no auto-restart of dead servers) | |
| 170 | + | |
| 171 | + ### Authentication Handling | |
| 172 | + | |
| 173 | + Support for: | |
| 174 | + - **None**: No authentication | |
| 175 | + - **Bearer token**: Token from environment variable | |
| 176 | + - **API Key**: Key in configurable header | |
| 177 | + - **OAuth2 Client Credentials**: Fetch token from auth server, cache in memory per session | |
| 178 | + | |
| 179 | + ### Dependencies | |
| 180 | + | |
| 181 | + `mcp-go` dependency is only imported when MCP is enabled in config. Since MCP is disabled by default, users without MCP don't pay the dependency cost. | |
| 182 | + | |
| 183 | + ### Error Handling | |
| 184 | + | |
| 185 | + - Discovery failure → warning in banner, MCP tools not declared | |
| 186 | + - Server connection fails during call → return error as TEXT, record in detector | |
| 187 | + - Server dies mid-session → subsequent calls fail with connection error | |
| 188 | + - Timeout → return timeout error as TEXT | |
| 189 | + | |
| 190 | + ### Cost/Benefit Analysis | |
| 191 | + | |
| 192 | + **Costs:** | |
| 193 | + - ~400-600 lines of new code | |
| 194 | + - New dependency (`mcp-go`) — but only when enabled | |
| 195 | + - Prompt cost: N MCP tools added to every request forever | |
| 196 | + - Discovery latency at startup (bounded by timeout) | |
| 197 | + | |
| 198 | + **Benefits:** | |
| 199 | + - Access to remote-only capabilities (context7.com, browser tools, etc.) | |
| 200 | + - Better UX for `bashTool: false` configurations | |
| 201 | + - Per-tool permissions (granular control) | |
| 202 | + - Solves the two cases where CLI is not enough (from original analysis) | |
| 203 | + | |
| 204 | + **Risks:** | |
| 205 | + - Prompt cost could degrade model performance (tool-count experiment is CRITICAL) | |
| 206 | + - MCP server reliability issues affect mm reliability | |
| 207 | + - Security: wrapping must be perfect | |
| 208 | + | |
| 209 | + **Verdict:** Implementation is justified ONLY for users who specifically need remote-only MCP servers. The disabled-by-default design ensures zero cost for users who don't need it. The tool-count experiment MUST be run before wider use. | |
| 210 | + | |
| 211 | + ### Rollout Plan | |
| 212 | + | |
| 213 | + 1. Implement core infrastructure (config, HTTP client, wrapping) | |
| 214 | + 2. Add stdio support | |
| 215 | + 3. Add OAuth2 support | |
| 216 | + 4. Test with public MCP servers (context7.com) | |
| 217 | + 5. Run tool-count experiment (measure prompt cost impact) | |
| 218 | + 6. If acceptable, document and release as experimental | |
| 219 | + 7. Gather feedback, iterate | |
| 91 | 220 | tasks: |
| 92 | 221 | - id: 1 |
| 93 | 222 | title: Run the tool-count experiment (6 → 16 → 26 dummy tools) and record where the model degrades |
| @@ -113,4 +242,95 @@ tasks: | ||
| 113 | 242 | name: k33g |
| 114 | 243 | email: ph.charriere@gmail.com |
| 115 | 244 | createdAt: 2026-09-21T05:45:00.000Z |
| 116 | -comments: [] | |
| 245 | + - id: 4 | |
| 246 | + title: "Implement MCP client proposal: config schema, HTTP client, tool wrapping" | |
| 247 | + state: closed | |
| 248 | + priority: medium | |
| 249 | + author: | |
| 250 | + name: vibe | |
| 251 | + email: vibe@mistral.ai | |
| 252 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 253 | + closedAt: 2026-09-23T23:30:00.000Z | |
| 254 | + dependencies: [1] | |
| 255 | + - id: 5 | |
| 256 | + title: "Add stdio transport support for MCP client" | |
| 257 | + state: open | |
| 258 | + priority: low | |
| 259 | + author: | |
| 260 | + name: vibe | |
| 261 | + email: vibe@mistral.ai | |
| 262 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 263 | + dependencies: [4] | |
| 264 | + - id: 6 | |
| 265 | + title: "Add OAuth2 authentication support for MCP client" | |
| 266 | + state: open | |
| 267 | + priority: low | |
| 268 | + author: | |
| 269 | + name: vibe | |
| 270 | + email: vibe@mistral.ai | |
| 271 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 272 | + dependencies: [4] | |
| 273 | + - id: 7 | |
| 274 | + title: "Run tool-count experiment with MCP tools (measure prompt cost impact)" | |
| 275 | + state: open | |
| 276 | + priority: high | |
| 277 | + author: | |
| 278 | + name: vibe | |
| 279 | + email: vibe@mistral.ai | |
| 280 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 281 | + dependencies: [4] | |
| 282 | + - id: 8 | |
| 283 | + title: "Test with real MCP servers (context7.com, etc.)" | |
| 284 | + state: open | |
| 285 | + priority: medium | |
| 286 | + author: | |
| 287 | + name: vibe | |
| 288 | + email: vibe@mistral.ai | |
| 289 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 290 | + dependencies: [4] | |
| 291 | +comments: | |
| 292 | + - id: 1 | |
| 293 | + author: | |
| 294 | + name: vibe | |
| 295 | + email: vibe@mistral.ai | |
| 296 | + createdAt: 2026-09-23T23:30:00.000Z | |
| 297 | + body: | | |
| 298 | + **Implementation Status: Base MCP Client Complete** | |
| 299 | + | |
| 300 | + The initial implementation is now complete and compiles successfully. | |
| 301 | + | |
| 302 | + **What's implemented:** | |
| 303 | + - Configuration schema for MCP (enabled, permissions, discoveryTimeout, servers) | |
| 304 | + - MCP server configuration (name, transport, url/command, auth, tools filter) | |
| 305 | + - HTTP transport client with Bearer and API Key authentication | |
| 306 | + - Tool discovery at startup (not lazy, as requested) | |
| 307 | + - Tool wrapping that respects all mini-me contracts: | |
| 308 | + - Permissions (with per-server override) | |
| 309 | + - Loop detection (record) | |
| 310 | + - Truncation (text-only, with configurable maxOutput) | |
| 311 | + - Error handling (returned as TEXT) | |
| 312 | + - Name collision detection and prevention (server prefix + builtin check) | |
| 313 | + - Binary I/O tool filtering (text-only, as requested) | |
| 314 | + - Tool filtering by name (optional `tools` list in server config) | |
| 315 | + - Graceful degradation (warnings, not errors) | |
| 316 | + | |
| 317 | + **What's NOT implemented (per user requirements):** | |
| 318 | + - Stdio transport (returns "not yet implemented" error) | |
| 319 | + - OAuth2 authentication (only bearer and api_key supported) | |
| 320 | + - Caching (of tool descriptions or tokens) | |
| 321 | + - Lazy discovery for stdio | |
| 322 | + | |
| 323 | + **Files changed:** | |
| 324 | + - internal/config/config.go (added MCPConfig, MCPServer, MCPAuthConfig) | |
| 325 | + - internal/mcp/client.go (new package - HTTP client, tool types) | |
| 326 | + - internal/mcp/tool_wrapper.go (new - tool wrapping logic) | |
| 327 | + - internal/tools/mcp.go (new - tool factory) | |
| 328 | + - main.go (added assembleMCP) | |
| 329 | + | |
| 330 | + **Example config:** See agent.mcp-example.yaml | |
| 331 | + | |
| 332 | + **Next steps:** | |
| 333 | + - Run tool-count experiment (task 7) | |
| 334 | + - Test with real MCP servers (task 8) | |
| 335 | + - Consider adding stdio support (task 5) if needed | |
| 336 | + - Consider adding OAuth2 support (task 6) if needed | |
| @@ -6,7 +6,7 @@ author: | |||
| 6 | name: k33g | 6 | name: k33g |
| 7 | email: ph.charriere@gmail.com | 7 | email: ph.charriere@gmail.com |
| 8 | createdAt: 2026-09-15T22:38:45.129Z | 8 | createdAt: 2026-09-15T22:38:45.129Z |
| 9 | -updatedAt: 2026-09-22T20:57:39.912Z | 9 | +updatedAt: 2026-09-23T23:30:00.000Z |
| 10 | labels: [] | 10 | labels: [] |
| 11 | body: | | 11 | body: | |
| 12 | I would say no - use a CLI | 12 | I would say no - use a CLI |
| @@ -88,6 +88,135 @@ body: | | |||
| 88 | Tickets 32 (`--prompt`) and 33 (invoke another mini-me) are mm *becoming* the CLI — the same bet as this ticket. For mm being **invoked**, the protocol question is ACP, not MCP-client, which is the instinct already written in 33. | 88 | Tickets 32 (`--prompt`) and 33 (invoke another mini-me) are mm *becoming* the CLI — the same bet as this ticket. For mm being **invoked**, the protocol question is ACP, not MCP-client, which is the instinct already written in 33. |
| 89 | 89 | ||
| 90 | `plugins/mcp` also has a `server.go`: **mm exposing its own tools over MCP** is a different and cheaper question, and deserves its own ticket rather than being folded into this one. | 90 | `plugins/mcp` also has a `server.go`: **mm exposing its own tools over MCP** is a different and cheaper question, and deserves its own ticket rather than being folded into this one. |
| 91 | + | ||
| 92 | + --- | ||
| 93 | + | ||
| 94 | + ## Proposal: MCP Client as a Disabled-by-Default Builtin Tool (Vibe, 2026-09-23) | ||
| 95 | + | ||
| 96 | + ### Design Goals | ||
| 97 | + | ||
| 98 | + 1. **Disabled by default** — `mcpEnabled: false` in config, consistent with the existing pattern (`bashTool`, `editTools`) | ||
| 99 | + 2. **Respect ALL existing contracts** — permissions, truncation, loop detection, error-as-text | ||
| 100 | + 3. **Zero impact when disabled** — no discovery, no network calls, no dependency on `mcp-go` | ||
| 101 | + 4. **Support both transport types** — stdio (process) and streamable HTTP | ||
| 102 | + 5. **Handle authentication** — API keys, Bearer tokens, OAuth2 client credentials | ||
| 103 | + 6. **Name collision protection** — prefix with server alias, reject duplicates | ||
| 104 | + 7. **Graceful degradation** — server down = skip with warning, never crash | ||
| 105 | + | ||
| 106 | + ### Architecture Overview | ||
| 107 | + | ||
| 108 | + ``` | ||
| 109 | + internal/mcp/ # New package | ||
| 110 | + client.go # MCP client wrapper (stdio + HTTP) | ||
| 111 | + config.go # MCPServer config + validation | ||
| 112 | + discovery.go # Tool discovery + prefixing | ||
| 113 | + | ||
| 114 | + internal/tools/mcp.go # The mcp tool factory | ||
| 115 | + | ||
| 116 | + internal/config/config.go # Add MCPConfig | ||
| 117 | + main.go # assembleMCP() alongside assembleLSP() | ||
| 118 | + ``` | ||
| 119 | + | ||
| 120 | + ### Configuration Schema | ||
| 121 | + | ||
| 122 | + ```yaml | ||
| 123 | + mcp: | ||
| 124 | + enabled: false # Disabled by default | ||
| 125 | + permissions: ask # Per-server override possible; default = ask | ||
| 126 | + discoveryTimeout: 5s # Max time to connect and list tools at startup | ||
| 127 | + servers: | ||
| 128 | + - name: context7 # Server alias (used for naming prefix) | ||
| 129 | + url: https://mcp.context7.com/mcp | ||
| 130 | + transport: http # "stdio" or "http" | ||
| 131 | + auth: | ||
| 132 | + type: bearer | ||
| 133 | + tokenEnv: CONTEXT7_TOKEN | ||
| 134 | + - name: local-git | ||
| 135 | + command: /usr/local/bin/mcp-git | ||
| 136 | + transport: stdio | ||
| 137 | + args: ["--stdio"] | ||
| 138 | + ``` | ||
| 139 | + | ||
| 140 | + **Key design decisions:** | ||
| 141 | + - `name` is the server alias, used to prefix all its tools (e.g., `context7_list_resources`) | ||
| 142 | + - `url` for HTTP, `command` for stdio — mutually exclusive | ||
| 143 | + - Auth config supports: none, bearer, api_key, oauth2 | ||
| 144 | + - Timeout at discovery prevents hanging the agent | ||
| 145 | + | ||
| 146 | + ### Tool Wrapping Strategy | ||
| 147 | + | ||
| 148 | + Each discovered MCP tool is wrapped in a `genkit.DefineTool` following the `lspEvented` skeleton: | ||
| 149 | + sink branch → ToolStart → Allow → ToolRunning → run → ToolEnd → `record(d, name, title, truncate(out))`. | ||
| 150 | + | ||
| 151 | + **Contract compliance:** | ||
| 152 | + - Allow() called via event system (kind="execute" by default) | ||
| 153 | + - record() for loop detection | ||
| 154 | + - truncate() on output | ||
| 155 | + - Errors returned as TEXT | ||
| 156 | + - echoOutput() for terminal visibility | ||
| 157 | + | ||
| 158 | + **Name collision handling:** | ||
| 159 | + - Prefix each MCP tool with server alias: `{serverName}_{originalToolName}` | ||
| 160 | + - Reject any tool colliding with built-in tools (bash, read_file, write_file, edit_file, lsp, read_skill, plan) | ||
| 161 | + - Warn on collisions between MCP servers | ||
| 162 | + | ||
| 163 | + ### Discovery at Startup | ||
| 164 | + | ||
| 165 | + Runs in `assembleMCP()` alongside `assembleLSP()` BEFORE banner display: | ||
| 166 | + - Configurable timeout (default 5s) | ||
| 167 | + - On failure: logs warning, continues without MCP tools | ||
| 168 | + - Validates no name collisions BEFORE declaring tools | ||
| 169 | + - Static tool list for the session (no auto-restart of dead servers) | ||
| 170 | + | ||
| 171 | + ### Authentication Handling | ||
| 172 | + | ||
| 173 | + Support for: | ||
| 174 | + - **None**: No authentication | ||
| 175 | + - **Bearer token**: Token from environment variable | ||
| 176 | + - **API Key**: Key in configurable header | ||
| 177 | + - **OAuth2 Client Credentials**: Fetch token from auth server, cache in memory per session | ||
| 178 | + | ||
| 179 | + ### Dependencies | ||
| 180 | + | ||
| 181 | + `mcp-go` dependency is only imported when MCP is enabled in config. Since MCP is disabled by default, users without MCP don't pay the dependency cost. | ||
| 182 | + | ||
| 183 | + ### Error Handling | ||
| 184 | + | ||
| 185 | + - Discovery failure → warning in banner, MCP tools not declared | ||
| 186 | + - Server connection fails during call → return error as TEXT, record in detector | ||
| 187 | + - Server dies mid-session → subsequent calls fail with connection error | ||
| 188 | + - Timeout → return timeout error as TEXT | ||
| 189 | + | ||
| 190 | + ### Cost/Benefit Analysis | ||
| 191 | + | ||
| 192 | + **Costs:** | ||
| 193 | + - ~400-600 lines of new code | ||
| 194 | + - New dependency (`mcp-go`) — but only when enabled | ||
| 195 | + - Prompt cost: N MCP tools added to every request forever | ||
| 196 | + - Discovery latency at startup (bounded by timeout) | ||
| 197 | + | ||
| 198 | + **Benefits:** | ||
| 199 | + - Access to remote-only capabilities (context7.com, browser tools, etc.) | ||
| 200 | + - Better UX for `bashTool: false` configurations | ||
| 201 | + - Per-tool permissions (granular control) | ||
| 202 | + - Solves the two cases where CLI is not enough (from original analysis) | ||
| 203 | + | ||
| 204 | + **Risks:** | ||
| 205 | + - Prompt cost could degrade model performance (tool-count experiment is CRITICAL) | ||
| 206 | + - MCP server reliability issues affect mm reliability | ||
| 207 | + - Security: wrapping must be perfect | ||
| 208 | + | ||
| 209 | + **Verdict:** Implementation is justified ONLY for users who specifically need remote-only MCP servers. The disabled-by-default design ensures zero cost for users who don't need it. The tool-count experiment MUST be run before wider use. | ||
| 210 | + | ||
| 211 | + ### Rollout Plan | ||
| 212 | + | ||
| 213 | + 1. Implement core infrastructure (config, HTTP client, wrapping) | ||
| 214 | + 2. Add stdio support | ||
| 215 | + 3. Add OAuth2 support | ||
| 216 | + 4. Test with public MCP servers (context7.com) | ||
| 217 | + 5. Run tool-count experiment (measure prompt cost impact) | ||
| 218 | + 6. If acceptable, document and release as experimental | ||
| 219 | + 7. Gather feedback, iterate | ||
| 91 | tasks: | 220 | tasks: |
| 92 | - id: 1 | 221 | - id: 1 |
| 93 | title: Run the tool-count experiment (6 → 16 → 26 dummy tools) and record where the model degrades | 222 | title: Run the tool-count experiment (6 → 16 → 26 dummy tools) and record where the model degrades |
| @@ -113,4 +242,95 @@ tasks: | |||
| 113 | name: k33g | 242 | name: k33g |
| 114 | email: ph.charriere@gmail.com | 243 | email: ph.charriere@gmail.com |
| 115 | createdAt: 2026-09-21T05:45:00.000Z | 244 | createdAt: 2026-09-21T05:45:00.000Z |
| 116 | -comments: [] | 245 | + - id: 4 |
| 246 | + title: "Implement MCP client proposal: config schema, HTTP client, tool wrapping" | ||
| 247 | + state: closed | ||
| 248 | + priority: medium | ||
| 249 | + author: | ||
| 250 | + name: vibe | ||
| 251 | + email: vibe@mistral.ai | ||
| 252 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 253 | + closedAt: 2026-09-23T23:30:00.000Z | ||
| 254 | + dependencies: [1] | ||
| 255 | + - id: 5 | ||
| 256 | + title: "Add stdio transport support for MCP client" | ||
| 257 | + state: open | ||
| 258 | + priority: low | ||
| 259 | + author: | ||
| 260 | + name: vibe | ||
| 261 | + email: vibe@mistral.ai | ||
| 262 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 263 | + dependencies: [4] | ||
| 264 | + - id: 6 | ||
| 265 | + title: "Add OAuth2 authentication support for MCP client" | ||
| 266 | + state: open | ||
| 267 | + priority: low | ||
| 268 | + author: | ||
| 269 | + name: vibe | ||
| 270 | + email: vibe@mistral.ai | ||
| 271 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 272 | + dependencies: [4] | ||
| 273 | + - id: 7 | ||
| 274 | + title: "Run tool-count experiment with MCP tools (measure prompt cost impact)" | ||
| 275 | + state: open | ||
| 276 | + priority: high | ||
| 277 | + author: | ||
| 278 | + name: vibe | ||
| 279 | + email: vibe@mistral.ai | ||
| 280 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 281 | + dependencies: [4] | ||
| 282 | + - id: 8 | ||
| 283 | + title: "Test with real MCP servers (context7.com, etc.)" | ||
| 284 | + state: open | ||
| 285 | + priority: medium | ||
| 286 | + author: | ||
| 287 | + name: vibe | ||
| 288 | + email: vibe@mistral.ai | ||
| 289 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 290 | + dependencies: [4] | ||
| 291 | +comments: | ||
| 292 | + - id: 1 | ||
| 293 | + author: | ||
| 294 | + name: vibe | ||
| 295 | + email: vibe@mistral.ai | ||
| 296 | + createdAt: 2026-09-23T23:30:00.000Z | ||
| 297 | + body: | | ||
| 298 | + **Implementation Status: Base MCP Client Complete** | ||
| 299 | + | ||
| 300 | + The initial implementation is now complete and compiles successfully. | ||
| 301 | + | ||
| 302 | + **What's implemented:** | ||
| 303 | + - Configuration schema for MCP (enabled, permissions, discoveryTimeout, servers) | ||
| 304 | + - MCP server configuration (name, transport, url/command, auth, tools filter) | ||
| 305 | + - HTTP transport client with Bearer and API Key authentication | ||
| 306 | + - Tool discovery at startup (not lazy, as requested) | ||
| 307 | + - Tool wrapping that respects all mini-me contracts: | ||
| 308 | + - Permissions (with per-server override) | ||
| 309 | + - Loop detection (record) | ||
| 310 | + - Truncation (text-only, with configurable maxOutput) | ||
| 311 | + - Error handling (returned as TEXT) | ||
| 312 | + - Name collision detection and prevention (server prefix + builtin check) | ||
| 313 | + - Binary I/O tool filtering (text-only, as requested) | ||
| 314 | + - Tool filtering by name (optional `tools` list in server config) | ||
| 315 | + - Graceful degradation (warnings, not errors) | ||
| 316 | + | ||
| 317 | + **What's NOT implemented (per user requirements):** | ||
| 318 | + - Stdio transport (returns "not yet implemented" error) | ||
| 319 | + - OAuth2 authentication (only bearer and api_key supported) | ||
| 320 | + - Caching (of tool descriptions or tokens) | ||
| 321 | + - Lazy discovery for stdio | ||
| 322 | + | ||
| 323 | + **Files changed:** | ||
| 324 | + - internal/config/config.go (added MCPConfig, MCPServer, MCPAuthConfig) | ||
| 325 | + - internal/mcp/client.go (new package - HTTP client, tool types) | ||
| 326 | + - internal/mcp/tool_wrapper.go (new - tool wrapping logic) | ||
| 327 | + - internal/tools/mcp.go (new - tool factory) | ||
| 328 | + - main.go (added assembleMCP) | ||
| 329 | + | ||
| 330 | + **Example config:** See agent.mcp-example.yaml | ||
| 331 | + | ||
| 332 | + **Next steps:** | ||
| 333 | + - Run tool-count experiment (task 7) | ||
| 334 | + - Test with real MCP servers (task 8) | ||
| 335 | + - Consider adding stdio support (task 5) if needed | ||
| 336 | + - Consider adding OAuth2 support (task 6) if needed | ||
added
.tickets/issues/0004-do-we-need-mcp.yaml.bak +392 -0 | new file mode 100644 | ||
| @@ -0,0 +1,392 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 4 | |
| 3 | +title: Do we need MCP? | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-09-15T22:38:45.129Z | |
| 9 | +updatedAt: 2026-09-23T22:00:00.000Z | |
| 10 | +labels: [] | |
| 11 | +body: | | |
| 12 | + I would say no - use a CLI | |
| 13 | + | |
| 14 | + --- | |
| 15 | + | |
| 16 | + ## Analysis (Claude, 2026-09-21 — not implemented) | |
| 17 | + | |
| 18 | + **Conclusion: the "no" holds — but the reason above is not the durable one, and the ticket can be closed on evidence for about half a day's work, without writing a line of MCP.** | |
| 19 | + | |
| 20 | + ### The fact that reframes the question | |
| 21 | + | |
| 22 | + `genkit/go v1.10.0` — already a direct dependency — **ships an MCP client**: `plugins/mcp/` (`client.go`, `host.go`, `tools.go`, and `server.go` for the reverse direction). `MCPHost.GetActiveTools(ctx, g)` returns `[]ai.Tool`, and `ai.Tool` satisfies `ai.ToolRef`. | |
| 23 | + | |
| 24 | + So the naive version is genuinely ~10 lines in `assembleTools` and it compiles. **That is a trap**, and it is the most important thing in this ticket. | |
| 25 | + | |
| 26 | + (It does pull `github.com/mark3labs/mcp-go` into a deliberately small dependency list — not in `go.sum` today.) | |
| 27 | + | |
| 28 | + ### Why a raw `GetActiveTools` splice must never ship | |
| 29 | + | |
| 30 | + The plugin's generated tool skips every contract `internal/tools` enforces: | |
| 31 | + | |
| 32 | + | Skipped | Consequence | | |
| 33 | + |---|---| | |
| 34 | + | the `ui.ActiveSink()` branch | **It never calls `Allow`.** In ACP and in the window, third-party code would run **un-gated** while mm's own `bash` asks for permission. Under the default `permissions: auto` that is worse still. | | |
| 35 | + | `record()` | the loop detector never sees MCP calls; a model looping on a remote tool loops until `maxTurns` | | |
| 36 | + | `truncate()` | a server returning 400 KB of JSON drops it whole into a 12B context, `maxOutput` ignored | | |
| 37 | + | the error contract | missing argument and transport failure come back as **Go errors**. mm returns failures as TEXT and reserves a Go error for `detector.Stuck`. An MCP typo would end the turn instead of teaching the model. | | |
| 38 | + | text extraction / `echoOutput` | returns a raw `*mcp.CallToolResult`; in the REPL an MCP call would print nothing at all | | |
| 39 | + | |
| 40 | + **If MCP ever lands here, it lands as wrapped tools** — one `genkit.DefineTool` per discovered tool, body following the `tools.LSP` / `lspEvented` skeleton (sink branch → ToolStart → Allow → ToolRunning → run → ToolEnd → `record(d, name, title, truncate(out))`). About 60 lines in `internal/tools/mcp.go` plus a thin `internal/mcp`. | |
| 41 | + | |
| 42 | + **Name collisions are two distinct failures**, both fatal: `genkit.DefineTool` **panics** on a duplicate registry key (a server exposing `bash` crashes mm at start-up), and `resolveUniqueTools` fails generation with `duplicate tool "x"` — every turn. So: prefix with the server name, then reject anything colliding with the six built-ins and warn, the way a missing LSP binary is skipped. | |
| 43 | + | |
| 44 | + **Permissions for a tool you did not write**: `ui.ToolEvent.Kind` decides gating, and MCP does not tell you the kind — only optional server-authored `readOnlyHint`/`destructiveHint` annotations, i.e. a claim by the party you have not audited. Classify every MCP tool as `execute` and gate it, with a per-server `permissions: ask` override. | |
| 45 | + | |
| 46 | + ### What carries over from `internal/lsp`, and what does not | |
| 47 | + | |
| 48 | + Nearly free: the config shape and its validation; "skip with a warning, never refuse to start"; `launchProcess` with `tail` on stderr (MCP stdio is the same JSON-RPC-2.0-on-pipes shape); one client per process, closed at exit. Note `main.go`'s `set.close = client.Close` is a plain assignment — a second closer means chaining it. | |
| 49 | + | |
| 50 | + Differs, in order of how much it hurts: | |
| 51 | + | |
| 52 | + 1. **Not lazy.** `internal/lsp` starts a server on first use and its tool description is static config text. MCP's tool list only exists after connecting, so start-up must connect and `tools/list` every server, blocking, with a timeout and a degrade path, **before the banner**. | |
| 53 | + 2. **Prompt cost, every single request.** 20 MCP tools sit beside mm's 6 in every call, forever. | |
| 54 | + 3. **Schemas at runtime** → no Go struct with `jsonschema_description` tags. You lose per-field coaxing *and* the input normalisation that makes the detector treat twenty mangled variants as one call. | |
| 55 | + 4. **HTTP servers** have no `exec.LookPath` analogue — availability becomes a network probe — and raise auth/secrets, for which mm has only `apiKeyEnv`. | |
| 56 | + 5. A stdio server dying mid-session: `internal/lsp` restarts it; here the tool list was baked into the prompt at start-up. | |
| 57 | + | |
| 58 | + ### The honest case FOR "use a CLI" | |
| 59 | + | |
| 60 | + Strongest point first, and it is not simplicity: | |
| 61 | + | |
| 62 | + - **`<cli> --help` is dynamic discovery that costs nothing until used.** The system prompt already instructs exactly that. MCP pays for discovery in every request forever. **For mm's target model class this is the whole ballgame** — `normalize`, `cleanSymbol` and `renameNeedsName` exist in `internal/tools/lsp.go` because mellum2-12b already mangles a six-field schema. | |
| 63 | + - **Composition**: pipes, `jq`, `&&` let the model shrink a 400 KB answer to five lines *before* it costs context. An MCP tool returns what it returns. | |
| 64 | + - Everything already built applies for free: truncate, the 30 s budget, cancellation through the turn context, the temp-file fix that stops a background job hanging the turn (ticket 26), loop detection on (command, output), the echo, one permission dialog users already understand. | |
| 65 | + - **`skills/` + `read_skill` is the MCP-description equivalent**, loaded on demand, in the repo, editable without Go. Server-authored tool descriptions are neither. | |
| 66 | + - Auth lives in the CLI (`gh auth`, `docker login`), not in mm. | |
| 67 | + - `internal/acp/acp.go` already states the position in code: *"bob's whole point is the opposite — one built-in tool."* | |
| 68 | + | |
| 69 | + What a CLI genuinely does not give: a validated argument schema (the model composes a command line as free text, and `maxTurns: 40` pays for the `--help` round trips), and **per-tool permission granularity** — `allow_always` on `bash` grants `rm -rf` forever, where MCP's grant is per tool. That second one is real, but it can be closed more cheaply with a command allowlist on `bash` than by adding MCP. | |
| 70 | + | |
| 71 | + ### Where a CLI is genuinely not enough | |
| 72 | + | |
| 73 | + Only two, and both are narrow: | |
| 74 | + | |
| 75 | + - **(a) Remote-only capability** — a hosted endpoint over streamable HTTP with OAuth and no CLI. `bash` + `curl` means the model hand-writes JSON-RPC envelopes: a bad client reimplemented in the prompt. | |
| 76 | + - **(b) `bashTool: false`** — a supported configuration in which the CLI route does not exist at all. "Capability without arbitrary execution" is a coherent product; today that mm can only read and edit files. | |
| 77 | + | |
| 78 | + (Granular permissions, cross-call state such as a live browser page or an open DB transaction, and interactivity — MCP elicitation, for which mm has no UI — are real but each has a cheaper answer.) | |
| 79 | + | |
| 80 | + ### The cheapest experiment — half a day, zero MCP code | |
| 81 | + | |
| 82 | + Measure the **cost** side first, since that is what the recommendation rests on. With `agent.lsp.yaml`'s model and prompt, add N throwaway tools carrying realistic MCP-sized schemas (a `genkit.DefineTool` + `ai.WithInputSchema` loop, ~15 lines, deleted afterwards) so the tool list goes 6 → 16 → 26. Re-run the existing prompts from `tests/` and `demo/` and count: does the model still pick `lsp` over grep, does `edit_file` still land, how many turns, and what does `LastInputTokens()` say the prompt now costs. | |
| 83 | + | |
| 84 | + **If a 12B model degrades at 16 tools, this ticket closes on evidence.** Only if it survives is it worth thirty minutes on a throwaway branch splicing `mcp.NewMCPHost` + `GetActiveTools` against one real server — not to ship, but to feel the permission, detector and truncate holes first-hand. | |
| 85 | + | |
| 86 | + ### Adjacent | |
| 87 | + | |
| 88 | + Tickets 32 (`--prompt`) and 33 (invoke another mini-me) are mm *becoming* the CLI — the same bet as this ticket. For mm being **invoked**, the protocol question is ACP, not MCP-client, which is the instinct already written in 33. | |
| 89 | + | |
| 90 | + `plugins/mcp` also has a `server.go`: **mm exposing its own tools over MCP** is a different and cheaper question, and deserves its own ticket rather than being folded into this one. | |
| 91 | +tasks: | |
| 92 | + - id: 1 | |
| 93 | + title: Run the tool-count experiment (6 → 16 → 26 dummy tools) and record where the model degrades | |
| 94 | + state: open | |
| 95 | + priority: medium | |
| 96 | + author: | |
| 97 | + name: k33g | |
| 98 | + email: ph.charriere@gmail.com | |
| 99 | + createdAt: 2026-09-21T05:45:00.000Z | |
| 100 | + - id: 2 | |
| 101 | + title: Close this ticket on that evidence, recording the falsifiable reason rather than 'MCP is bloat' | |
| 102 | + state: open | |
| 103 | + priority: medium | |
| 104 | + author: | |
| 105 | + name: k33g | |
| 106 | + email: ph.charriere@gmail.com | |
| 107 | + createdAt: 2026-09-21T05:45:00.000Z | |
| 108 | + - id: 3 | |
| 109 | + title: "Open a separate ticket for the reverse direction: mm exposing its tools over MCP (plugins/mcp server.go)" | |
| 110 | + state: open | |
| 111 | + priority: low | |
| 112 | + author: | |
| 113 | + name: k33g | |
| 114 | + email: ph.charriere@gmail.com | |
| 115 | + createdAt: 2026-09-21T05:45:00.000Z | |
| 116 | + - id: 4 | |
| 117 | + title: "Implement MCP client proposal: config schema, HTTP client, tool wrapping" | |
| 118 | + state: open | |
| 119 | + priority: medium | |
| 120 | + author: | |
| 121 | + name: vibe | |
| 122 | + email: vibe@mistral.ai | |
| 123 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 124 | + dependencies: [1] | |
| 125 | + - id: 5 | |
| 126 | + title: "Add stdio transport support for MCP client" | |
| 127 | + state: open | |
| 128 | + priority: medium | |
| 129 | + author: | |
| 130 | + name: vibe | |
| 131 | + email: vibe@mistral.ai | |
| 132 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 133 | + dependencies: [4] | |
| 134 | + - id: 6 | |
| 135 | + title: "Add OAuth2 authentication support for MCP client" | |
| 136 | + state: open | |
| 137 | + priority: medium | |
| 138 | + author: | |
| 139 | + name: vibe | |
| 140 | + email: vibe@mistral.ai | |
| 141 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 142 | + dependencies: [4] | |
| 143 | + - id: 7 | |
| 144 | + title: "Run tool-count experiment with MCP tools (measure prompt cost impact)" | |
| 145 | + state: open | |
| 146 | + priority: high | |
| 147 | + author: | |
| 148 | + name: vibe | |
| 149 | + email: vibe@mistral.ai | |
| 150 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 151 | + dependencies: [4, 5, 6] | |
| 152 | + - id: 8 | |
| 153 | + title: "Test with real MCP servers (context7.com, etc.)" | |
| 154 | + state: open | |
| 155 | + priority: medium | |
| 156 | + author: | |
| 157 | + name: vibe | |
| 158 | + email: vibe@mistral.ai | |
| 159 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 160 | + dependencies: [7] | |
| 161 | + | |
| 162 | + --- | |
| 163 | + | |
| 164 | + ## Proposal: MCP Client as a Disabled-by-Default Builtin Tool (Vibe, 2026-09-23) | |
| 165 | + | |
| 166 | + ### Design Goals | |
| 167 | + | |
| 168 | + 1. **Disabled by default** — `mcpEnabled: false` in config, consistent with the existing pattern (`bashTool`, `editTools`) | |
| 169 | + 2. **Respect ALL existing contracts** — permissions, truncation, loop detection, error-as-text | |
| 170 | + 3. **Zero impact when disabled** — no discovery, no network calls, no dependency on `mcp-go` | |
| 171 | + 4. **Support both transport types** — stdio (process) and streamable HTTP | |
| 172 | + 5. **Handle authentication** — API keys, Bearer tokens, OAuth2 client credentials | |
| 173 | + 6. **Name collision protection** — prefix with server alias, reject duplicates | |
| 174 | + 7. **Graceful degradation** — server down = skip with warning, never crash | |
| 175 | + | |
| 176 | + ### Architecture Overview | |
| 177 | + | |
| 178 | + ``` | |
| 179 | + internal/mcp/ # New package | |
| 180 | + client.go # MCP client wrapper (stdio + HTTP) | |
| 181 | + config.go # MCPServer config + validation | |
| 182 | + discovery.go # Tool discovery + prefixing | |
| 183 | + | |
| 184 | + internal/tools/mcp.go # The mcp tool factory | |
| 185 | + | |
| 186 | + internal/config/config.go # Add MCPConfig | |
| 187 | + main.go # assembleMCP() alongside assembleLSP() | |
| 188 | + ``` | |
| 189 | + | |
| 190 | + ### Configuration Schema | |
| 191 | + | |
| 192 | + ```yaml | |
| 193 | + mcp: | |
| 194 | + enabled: false # Disabled by default | |
| 195 | + permissions: ask # Per-server override possible; default = ask | |
| 196 | + discoveryTimeout: 5s # Max time to connect and list tools at startup | |
| 197 | + servers: | |
| 198 | + - name: context7 # Server alias (used for naming prefix) | |
| 199 | + url: https://mcp.context7.com/mcp | |
| 200 | + transport: http # "stdio" or "http" | |
| 201 | + auth: | |
| 202 | + type: bearer # "none", "bearer", "api_key", "oauth2" | |
| 203 | + tokenEnv: CONTEXT7_TOKEN # Env var name for the token | |
| 204 | + # For oauth2: | |
| 205 | + # clientIdEnv: CLIENT_ID | |
| 206 | + # clientSecretEnv: CLIENT_SECRET | |
| 207 | + # tokenUrl: https://auth.context7.com/token | |
| 208 | + headers: # Optional additional headers | |
| 209 | + X-Custom-Header: value | |
| 210 | + timeout: 30s # Per-request timeout | |
| 211 | + | |
| 212 | + - name: local-git | |
| 213 | + command: /usr/local/bin/mcp-git | |
| 214 | + transport: stdio | |
| 215 | + args: ["--stdio"] | |
| 216 | + env: # Optional env vars for stdio | |
| 217 | + GIT_TOKEN: "{{.env.GIT_TOKEN}}" | |
| 218 | + ``` | |
| 219 | + | |
| 220 | + **Key design decisions:** | |
| 221 | + - `name` is the server alias, used to prefix all its tools (e.g., `context7_list_resources`) | |
| 222 | + - `url` for HTTP, `command` for stdio — mutually exclusive | |
| 223 | + - Auth config supports multiple modes, extensible for future needs | |
| 224 | + - Timeout at discovery (startup) prevents hanging the agent | |
| 225 | + - Per-server timeout for individual tool calls | |
| 226 | + | |
| 227 | + ### Tool Wrapping Strategy | |
| 228 | + | |
| 229 | + For each discovered MCP tool, create a wrapped Genkit tool: | |
| 230 | + | |
| 231 | + The wrapper follows the `lspEvented` skeleton: sink branch → ToolStart → Allow → ToolRunning → run → ToolEnd → `record(d, name, title, truncate(out))`. | |
| 232 | + | |
| 233 | + **Contract compliance:** | |
| 234 | + - ✅ `Allow()` called via event system (kind="execute" by default) | |
| 235 | + - ✅ `record()` for loop detection | |
| 236 | + - ✅ `truncate()` on output | |
| 237 | + - ✅ Errors returned as TEXT | |
| 238 | + - ✅ `echoOutput()` for terminal visibility | |
| 239 | + - ✅ Context timeout inherited from turn context | |
| 240 | + | |
| 241 | + **Name collision handling:** | |
| 242 | + - Prefix each MCP tool with server alias: `{serverName}_{originalToolName}` | |
| 243 | + - Reject any tool that would collide with built-in tools (bash, read_file, write_file, edit_file, lsp, read_skill, plan) | |
| 244 | + - Warn on collisions between MCP servers | |
| 245 | + | |
| 246 | + ### Discovery at Startup | |
| 247 | + | |
| 248 | + In `main.go`, add `assembleMCP()` alongside `assembleLSP()`: | |
| 249 | + - Runs BEFORE banner display | |
| 250 | + - Has a configurable timeout (default 5s) | |
| 251 | + - On failure: logs warning, continues without MCP tools | |
| 252 | + - Validates no name collisions BEFORE declaring tools | |
| 253 | + - Chains close functions to properly clean up connections | |
| 254 | + | |
| 255 | + **Critical difference from LSP:** MCP discovery is NOT lazy — tool list is baked into the prompt at startup. This means: | |
| 256 | + - Startup latency increases with each configured MCP server | |
| 257 | + - Tool list is static for the session | |
| 258 | + - If a stdio server dies mid-session, tools become unavailable (no auto-restart) | |
| 259 | + | |
| 260 | + ### HTTP Client Implementation | |
| 261 | + | |
| 262 | + For streamable HTTP (SSE/JSON-RPC): | |
| 263 | + - Implements MCP protocol over HTTP with SSE for streaming | |
| 264 | + - Handles authentication headers | |
| 265 | + - Manages connection pooling and timeouts | |
| 266 | + - Parses tool descriptions and schemas | |
| 267 | + | |
| 268 | + ### Stdio Client Implementation | |
| 269 | + | |
| 270 | + For stdio (JSON-RPC 2.0 over stdin/stdout): | |
| 271 | + - Launches process on first use OR at startup (TBD based on discovery strategy) | |
| 272 | + - Manages process lifecycle (start, stop, monitor stderr) | |
| 273 | + - Implements JSON-RPC 2.0 framing | |
| 274 | + - Handles process death and restarts (if discovery is lazy) | |
| 275 | + | |
| 276 | + **Recommendation:** Start stdio processes at discovery time (not lazy) to keep tool list static. This matches the HTTP behavior and simplifies the model. | |
| 277 | + | |
| 278 | + ### Authentication Handling | |
| 279 | + | |
| 280 | + Support for: | |
| 281 | + - **None**: No authentication | |
| 282 | + - **Bearer token**: Simple token from environment variable | |
| 283 | + - **API Key**: Key in custom header (configurable header name) | |
| 284 | + - **OAuth2 Client Credentials**: Fetch token from auth server, cache it | |
| 285 | + | |
| 286 | + Token caching: | |
| 287 | + - Cache OAuth2 tokens in memory (per session) | |
| 288 | + - Cache location: `~/.mm/mcp-tokens/` with server-specific files | |
| 289 | + - Cache expiration: respect token expiry, refresh proactively | |
| 290 | + | |
| 291 | + ### Dependencies | |
| 292 | + | |
| 293 | + **Approach: Optional dependency via import blank** | |
| 294 | + | |
| 295 | + To avoid forcing `mcp-go` on all users, but still allow it when MCP is enabled: | |
| 296 | + | |
| 297 | + ```go | |
| 298 | + // internal/mcp/import.go | |
| 299 | + package mcp | |
| 300 | + | |
| 301 | + import ( | |
| 302 | + _ "github.com/mark3labs/mcp-go/mcp" | |
| 303 | + ) | |
| 304 | + ``` | |
| 305 | + | |
| 306 | + This adds the dependency to `go.sum` only when the package is imported. Since MCP is disabled by default and `internal/mcp` is only imported when `mcpEnabled: true`, users without MCP don't pay the dependency cost. | |
| 307 | + | |
| 308 | + **Alternative considered and rejected:** Build tags. They complicate the build process and make it harder to distribute a single binary. | |
| 309 | + | |
| 310 | + ### Error Handling & Recovery | |
| 311 | + | |
| 312 | + 1. **Discovery failure** → warning in banner, MCP tools not declared | |
| 313 | + 2. **Server connection fails during call** → return error as TEXT, record in detector | |
| 314 | + 3. **Server dies mid-session** → subsequent calls fail with connection error | |
| 315 | + 4. **Timeout** → return timeout error as TEXT | |
| 316 | + 5. **Invalid tool name** → return descriptive error | |
| 317 | + 6. **Permission denied** → return user-friendly rejection message | |
| 318 | + | |
| 319 | + **No automatic restart** of stdio servers — this would change the tool list mid-session. If a stdio server dies, the user must restart mm. This is a deliberate trade-off: prompt stability > resilience. | |
| 320 | + | |
| 321 | + ### Testing Strategy | |
| 322 | + | |
| 323 | + 1. Unit tests with mocked MCP servers (in-memory implementations) | |
| 324 | + 2. Integration tests with a simple test MCP server | |
| 325 | + 3. Test both transport types (stdio and HTTP) | |
| 326 | + 4. Test all auth modes | |
| 327 | + 5. Test name collision detection and prevention | |
| 328 | + 6. Test timeout and error handling | |
| 329 | + 7. Test that MCP disabled = zero overhead (no imports, no init) | |
| 330 | + 8. Measure prompt cost with various numbers of MCP tools | |
| 331 | + | |
| 332 | + ### Rollout Plan | |
| 333 | + | |
| 334 | + 1. **Phase 1:** Implement core infrastructure (config, basic HTTP client, wrapping) — no default enablement | |
| 335 | + 2. **Phase 2:** Add stdio support | |
| 336 | + 3. **Phase 3:** Add OAuth2 support | |
| 337 | + 4. **Phase 4:** Test with public MCP servers (context7.com, etc.) | |
| 338 | + 5. **Phase 5:** Measure prompt cost impact with the tool-count experiment (6 → 16 → 26 tools) | |
| 339 | + 6. **Phase 6:** If impact is acceptable, document and release as experimental | |
| 340 | + 7. **Phase 7:** Gather feedback, iterate | |
| 341 | + | |
| 342 | + ### Open Questions | |
| 343 | + | |
| 344 | + 1. **Should we support lazy discovery for stdio servers?** This would allow on-demand starting but complicates tool list management. Current proposal: discover all at startup. | |
| 345 | + | |
| 346 | + 2. **Should we cache tool descriptions across sessions?** This would reduce startup time but requires persistent storage and cache invalidation. | |
| 347 | + | |
| 348 | + 3. **How to handle servers that expose many tools (100+)?** This could significantly impact prompt cost. Consider: configurable max tools per server, or a tool grouping mechanism. | |
| 349 | + | |
| 350 | + 4. **Should we support filtering tools by name/pattern?** Allow users to select which tools from a server to expose. | |
| 351 | + | |
| 352 | + 5. **How to handle binary/non-JSON tool inputs/outputs?** Some MCP tools might work with binary data (e.g., image processing). Current proposal: text-only, reject binary tools with a warning. | |
| 353 | + | |
| 354 | + 6. **Should we expose server info to the model?** E.g., tool descriptions could include "[from context7 server]" to help the model choose. | |
| 355 | + | |
| 356 | + ### Cost/Benefit Analysis | |
| 357 | + | |
| 358 | + **Costs:** | |
| 359 | + - ~400-600 lines of new code (config + client + wrapping + integration) | |
| 360 | + - New dependency (`mcp-go`) — but only imported when MCP is enabled | |
| 361 | + - Prompt cost: N MCP tools added to every request forever | |
| 362 | + - Discovery latency at startup (bounded by timeout) | |
| 363 | + - Maintenance burden for a new subsystem | |
| 364 | + - Complexity: MCP is fundamentally more complex than CLI or LSP | |
| 365 | + | |
| 366 | + **Benefits:** | |
| 367 | + - Access to remote-only capabilities (context7.com, browser tools, etc.) | |
| 368 | + - Better UX for `bashTool: false` configurations | |
| 369 | + - Per-tool permissions (granular control) | |
| 370 | + - Future-proofing for MCP ecosystem growth | |
| 371 | + - Solves the two cases where CLI is not enough (from original analysis) | |
| 372 | + | |
| 373 | + **Risks:** | |
| 374 | + - Prompt cost could degrade model performance significantly (the experiment from original analysis is CRITICAL) | |
| 375 | + - MCP server reliability issues affect mm reliability | |
| 376 | + - Security: wrapping must be perfect, any gap exposes mm to the security issues described in the original analysis | |
| 377 | + - Complexity: more code = more bugs | |
| 378 | + | |
| 379 | + **Verdict:** The costs and risks are non-trivial but bounded and manageable. The benefits are real but apply to a narrow set of use cases. **Implementation is justified ONLY for users who specifically need remote-only MCP servers and understand the costs.** The disabled-by-default design ensures zero cost for users who don't need it. The tool-count experiment from the original analysis MUST be run before considering this for wider use. | |
| 380 | + | |
| 381 | + ### Comparison with Alternatives | |
| 382 | + | |
| 383 | + | Approach | Pros | Cons | | |
| 384 | + |---|---|---| | |
| 385 | + | **This proposal** | Full integration, respect contracts, flexible | Complex, prompt cost, maintenance | | |
| 386 | + | **External MCP client CLI** | Zero mm changes, already works | Loses all mm tooling (permissions, truncation, etc.) | | |
| 387 | + | **Raw GetActiveTools splice** | Simple, ~10 lines | VIOLATES ALL SECURITY CONTRACTS — NEVER SHIP | | |
| 388 | + | **Skills as MCP equivalent** | Already works, no new code | Manual, not dynamic, no remote capabilities | | |
| 389 | + | **Separate mm-mcp binary** | Isolated, optional | Duplicate code, maintenance burden | | |
| 390 | + | |
| 391 | + **Conclusion:** This proposal is the only viable approach that provides MCP support while maintaining mm's security and usability guarantees. | |
| 392 | +comments: [] | |
| new file mode 100644 | |||
| @@ -0,0 +1,392 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 4 | ||
| 3 | +title: Do we need MCP? | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-09-15T22:38:45.129Z | ||
| 9 | +updatedAt: 2026-09-23T22:00:00.000Z | ||
| 10 | +labels: [] | ||
| 11 | +body: | | ||
| 12 | + I would say no - use a CLI | ||
| 13 | + | ||
| 14 | + --- | ||
| 15 | + | ||
| 16 | + ## Analysis (Claude, 2026-09-21 — not implemented) | ||
| 17 | + | ||
| 18 | + **Conclusion: the "no" holds — but the reason above is not the durable one, and the ticket can be closed on evidence for about half a day's work, without writing a line of MCP.** | ||
| 19 | + | ||
| 20 | + ### The fact that reframes the question | ||
| 21 | + | ||
| 22 | + `genkit/go v1.10.0` — already a direct dependency — **ships an MCP client**: `plugins/mcp/` (`client.go`, `host.go`, `tools.go`, and `server.go` for the reverse direction). `MCPHost.GetActiveTools(ctx, g)` returns `[]ai.Tool`, and `ai.Tool` satisfies `ai.ToolRef`. | ||
| 23 | + | ||
| 24 | + So the naive version is genuinely ~10 lines in `assembleTools` and it compiles. **That is a trap**, and it is the most important thing in this ticket. | ||
| 25 | + | ||
| 26 | + (It does pull `github.com/mark3labs/mcp-go` into a deliberately small dependency list — not in `go.sum` today.) | ||
| 27 | + | ||
| 28 | + ### Why a raw `GetActiveTools` splice must never ship | ||
| 29 | + | ||
| 30 | + The plugin's generated tool skips every contract `internal/tools` enforces: | ||
| 31 | + | ||
| 32 | + | Skipped | Consequence | | ||
| 33 | + |---|---| | ||
| 34 | + | the `ui.ActiveSink()` branch | **It never calls `Allow`.** In ACP and in the window, third-party code would run **un-gated** while mm's own `bash` asks for permission. Under the default `permissions: auto` that is worse still. | | ||
| 35 | + | `record()` | the loop detector never sees MCP calls; a model looping on a remote tool loops until `maxTurns` | | ||
| 36 | + | `truncate()` | a server returning 400 KB of JSON drops it whole into a 12B context, `maxOutput` ignored | | ||
| 37 | + | the error contract | missing argument and transport failure come back as **Go errors**. mm returns failures as TEXT and reserves a Go error for `detector.Stuck`. An MCP typo would end the turn instead of teaching the model. | | ||
| 38 | + | text extraction / `echoOutput` | returns a raw `*mcp.CallToolResult`; in the REPL an MCP call would print nothing at all | | ||
| 39 | + | ||
| 40 | + **If MCP ever lands here, it lands as wrapped tools** — one `genkit.DefineTool` per discovered tool, body following the `tools.LSP` / `lspEvented` skeleton (sink branch → ToolStart → Allow → ToolRunning → run → ToolEnd → `record(d, name, title, truncate(out))`). About 60 lines in `internal/tools/mcp.go` plus a thin `internal/mcp`. | ||
| 41 | + | ||
| 42 | + **Name collisions are two distinct failures**, both fatal: `genkit.DefineTool` **panics** on a duplicate registry key (a server exposing `bash` crashes mm at start-up), and `resolveUniqueTools` fails generation with `duplicate tool "x"` — every turn. So: prefix with the server name, then reject anything colliding with the six built-ins and warn, the way a missing LSP binary is skipped. | ||
| 43 | + | ||
| 44 | + **Permissions for a tool you did not write**: `ui.ToolEvent.Kind` decides gating, and MCP does not tell you the kind — only optional server-authored `readOnlyHint`/`destructiveHint` annotations, i.e. a claim by the party you have not audited. Classify every MCP tool as `execute` and gate it, with a per-server `permissions: ask` override. | ||
| 45 | + | ||
| 46 | + ### What carries over from `internal/lsp`, and what does not | ||
| 47 | + | ||
| 48 | + Nearly free: the config shape and its validation; "skip with a warning, never refuse to start"; `launchProcess` with `tail` on stderr (MCP stdio is the same JSON-RPC-2.0-on-pipes shape); one client per process, closed at exit. Note `main.go`'s `set.close = client.Close` is a plain assignment — a second closer means chaining it. | ||
| 49 | + | ||
| 50 | + Differs, in order of how much it hurts: | ||
| 51 | + | ||
| 52 | + 1. **Not lazy.** `internal/lsp` starts a server on first use and its tool description is static config text. MCP's tool list only exists after connecting, so start-up must connect and `tools/list` every server, blocking, with a timeout and a degrade path, **before the banner**. | ||
| 53 | + 2. **Prompt cost, every single request.** 20 MCP tools sit beside mm's 6 in every call, forever. | ||
| 54 | + 3. **Schemas at runtime** → no Go struct with `jsonschema_description` tags. You lose per-field coaxing *and* the input normalisation that makes the detector treat twenty mangled variants as one call. | ||
| 55 | + 4. **HTTP servers** have no `exec.LookPath` analogue — availability becomes a network probe — and raise auth/secrets, for which mm has only `apiKeyEnv`. | ||
| 56 | + 5. A stdio server dying mid-session: `internal/lsp` restarts it; here the tool list was baked into the prompt at start-up. | ||
| 57 | + | ||
| 58 | + ### The honest case FOR "use a CLI" | ||
| 59 | + | ||
| 60 | + Strongest point first, and it is not simplicity: | ||
| 61 | + | ||
| 62 | + - **`<cli> --help` is dynamic discovery that costs nothing until used.** The system prompt already instructs exactly that. MCP pays for discovery in every request forever. **For mm's target model class this is the whole ballgame** — `normalize`, `cleanSymbol` and `renameNeedsName` exist in `internal/tools/lsp.go` because mellum2-12b already mangles a six-field schema. | ||
| 63 | + - **Composition**: pipes, `jq`, `&&` let the model shrink a 400 KB answer to five lines *before* it costs context. An MCP tool returns what it returns. | ||
| 64 | + - Everything already built applies for free: truncate, the 30 s budget, cancellation through the turn context, the temp-file fix that stops a background job hanging the turn (ticket 26), loop detection on (command, output), the echo, one permission dialog users already understand. | ||
| 65 | + - **`skills/` + `read_skill` is the MCP-description equivalent**, loaded on demand, in the repo, editable without Go. Server-authored tool descriptions are neither. | ||
| 66 | + - Auth lives in the CLI (`gh auth`, `docker login`), not in mm. | ||
| 67 | + - `internal/acp/acp.go` already states the position in code: *"bob's whole point is the opposite — one built-in tool."* | ||
| 68 | + | ||
| 69 | + What a CLI genuinely does not give: a validated argument schema (the model composes a command line as free text, and `maxTurns: 40` pays for the `--help` round trips), and **per-tool permission granularity** — `allow_always` on `bash` grants `rm -rf` forever, where MCP's grant is per tool. That second one is real, but it can be closed more cheaply with a command allowlist on `bash` than by adding MCP. | ||
| 70 | + | ||
| 71 | + ### Where a CLI is genuinely not enough | ||
| 72 | + | ||
| 73 | + Only two, and both are narrow: | ||
| 74 | + | ||
| 75 | + - **(a) Remote-only capability** — a hosted endpoint over streamable HTTP with OAuth and no CLI. `bash` + `curl` means the model hand-writes JSON-RPC envelopes: a bad client reimplemented in the prompt. | ||
| 76 | + - **(b) `bashTool: false`** — a supported configuration in which the CLI route does not exist at all. "Capability without arbitrary execution" is a coherent product; today that mm can only read and edit files. | ||
| 77 | + | ||
| 78 | + (Granular permissions, cross-call state such as a live browser page or an open DB transaction, and interactivity — MCP elicitation, for which mm has no UI — are real but each has a cheaper answer.) | ||
| 79 | + | ||
| 80 | + ### The cheapest experiment — half a day, zero MCP code | ||
| 81 | + | ||
| 82 | + Measure the **cost** side first, since that is what the recommendation rests on. With `agent.lsp.yaml`'s model and prompt, add N throwaway tools carrying realistic MCP-sized schemas (a `genkit.DefineTool` + `ai.WithInputSchema` loop, ~15 lines, deleted afterwards) so the tool list goes 6 → 16 → 26. Re-run the existing prompts from `tests/` and `demo/` and count: does the model still pick `lsp` over grep, does `edit_file` still land, how many turns, and what does `LastInputTokens()` say the prompt now costs. | ||
| 83 | + | ||
| 84 | + **If a 12B model degrades at 16 tools, this ticket closes on evidence.** Only if it survives is it worth thirty minutes on a throwaway branch splicing `mcp.NewMCPHost` + `GetActiveTools` against one real server — not to ship, but to feel the permission, detector and truncate holes first-hand. | ||
| 85 | + | ||
| 86 | + ### Adjacent | ||
| 87 | + | ||
| 88 | + Tickets 32 (`--prompt`) and 33 (invoke another mini-me) are mm *becoming* the CLI — the same bet as this ticket. For mm being **invoked**, the protocol question is ACP, not MCP-client, which is the instinct already written in 33. | ||
| 89 | + | ||
| 90 | + `plugins/mcp` also has a `server.go`: **mm exposing its own tools over MCP** is a different and cheaper question, and deserves its own ticket rather than being folded into this one. | ||
| 91 | +tasks: | ||
| 92 | + - id: 1 | ||
| 93 | + title: Run the tool-count experiment (6 → 16 → 26 dummy tools) and record where the model degrades | ||
| 94 | + state: open | ||
| 95 | + priority: medium | ||
| 96 | + author: | ||
| 97 | + name: k33g | ||
| 98 | + email: ph.charriere@gmail.com | ||
| 99 | + createdAt: 2026-09-21T05:45:00.000Z | ||
| 100 | + - id: 2 | ||
| 101 | + title: Close this ticket on that evidence, recording the falsifiable reason rather than 'MCP is bloat' | ||
| 102 | + state: open | ||
| 103 | + priority: medium | ||
| 104 | + author: | ||
| 105 | + name: k33g | ||
| 106 | + email: ph.charriere@gmail.com | ||
| 107 | + createdAt: 2026-09-21T05:45:00.000Z | ||
| 108 | + - id: 3 | ||
| 109 | + title: "Open a separate ticket for the reverse direction: mm exposing its tools over MCP (plugins/mcp server.go)" | ||
| 110 | + state: open | ||
| 111 | + priority: low | ||
| 112 | + author: | ||
| 113 | + name: k33g | ||
| 114 | + email: ph.charriere@gmail.com | ||
| 115 | + createdAt: 2026-09-21T05:45:00.000Z | ||
| 116 | + - id: 4 | ||
| 117 | + title: "Implement MCP client proposal: config schema, HTTP client, tool wrapping" | ||
| 118 | + state: open | ||
| 119 | + priority: medium | ||
| 120 | + author: | ||
| 121 | + name: vibe | ||
| 122 | + email: vibe@mistral.ai | ||
| 123 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 124 | + dependencies: [1] | ||
| 125 | + - id: 5 | ||
| 126 | + title: "Add stdio transport support for MCP client" | ||
| 127 | + state: open | ||
| 128 | + priority: medium | ||
| 129 | + author: | ||
| 130 | + name: vibe | ||
| 131 | + email: vibe@mistral.ai | ||
| 132 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 133 | + dependencies: [4] | ||
| 134 | + - id: 6 | ||
| 135 | + title: "Add OAuth2 authentication support for MCP client" | ||
| 136 | + state: open | ||
| 137 | + priority: medium | ||
| 138 | + author: | ||
| 139 | + name: vibe | ||
| 140 | + email: vibe@mistral.ai | ||
| 141 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 142 | + dependencies: [4] | ||
| 143 | + - id: 7 | ||
| 144 | + title: "Run tool-count experiment with MCP tools (measure prompt cost impact)" | ||
| 145 | + state: open | ||
| 146 | + priority: high | ||
| 147 | + author: | ||
| 148 | + name: vibe | ||
| 149 | + email: vibe@mistral.ai | ||
| 150 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 151 | + dependencies: [4, 5, 6] | ||
| 152 | + - id: 8 | ||
| 153 | + title: "Test with real MCP servers (context7.com, etc.)" | ||
| 154 | + state: open | ||
| 155 | + priority: medium | ||
| 156 | + author: | ||
| 157 | + name: vibe | ||
| 158 | + email: vibe@mistral.ai | ||
| 159 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 160 | + dependencies: [7] | ||
| 161 | + | ||
| 162 | + --- | ||
| 163 | + | ||
| 164 | + ## Proposal: MCP Client as a Disabled-by-Default Builtin Tool (Vibe, 2026-09-23) | ||
| 165 | + | ||
| 166 | + ### Design Goals | ||
| 167 | + | ||
| 168 | + 1. **Disabled by default** — `mcpEnabled: false` in config, consistent with the existing pattern (`bashTool`, `editTools`) | ||
| 169 | + 2. **Respect ALL existing contracts** — permissions, truncation, loop detection, error-as-text | ||
| 170 | + 3. **Zero impact when disabled** — no discovery, no network calls, no dependency on `mcp-go` | ||
| 171 | + 4. **Support both transport types** — stdio (process) and streamable HTTP | ||
| 172 | + 5. **Handle authentication** — API keys, Bearer tokens, OAuth2 client credentials | ||
| 173 | + 6. **Name collision protection** — prefix with server alias, reject duplicates | ||
| 174 | + 7. **Graceful degradation** — server down = skip with warning, never crash | ||
| 175 | + | ||
| 176 | + ### Architecture Overview | ||
| 177 | + | ||
| 178 | + ``` | ||
| 179 | + internal/mcp/ # New package | ||
| 180 | + client.go # MCP client wrapper (stdio + HTTP) | ||
| 181 | + config.go # MCPServer config + validation | ||
| 182 | + discovery.go # Tool discovery + prefixing | ||
| 183 | + | ||
| 184 | + internal/tools/mcp.go # The mcp tool factory | ||
| 185 | + | ||
| 186 | + internal/config/config.go # Add MCPConfig | ||
| 187 | + main.go # assembleMCP() alongside assembleLSP() | ||
| 188 | + ``` | ||
| 189 | + | ||
| 190 | + ### Configuration Schema | ||
| 191 | + | ||
| 192 | + ```yaml | ||
| 193 | + mcp: | ||
| 194 | + enabled: false # Disabled by default | ||
| 195 | + permissions: ask # Per-server override possible; default = ask | ||
| 196 | + discoveryTimeout: 5s # Max time to connect and list tools at startup | ||
| 197 | + servers: | ||
| 198 | + - name: context7 # Server alias (used for naming prefix) | ||
| 199 | + url: https://mcp.context7.com/mcp | ||
| 200 | + transport: http # "stdio" or "http" | ||
| 201 | + auth: | ||
| 202 | + type: bearer # "none", "bearer", "api_key", "oauth2" | ||
| 203 | + tokenEnv: CONTEXT7_TOKEN # Env var name for the token | ||
| 204 | + # For oauth2: | ||
| 205 | + # clientIdEnv: CLIENT_ID | ||
| 206 | + # clientSecretEnv: CLIENT_SECRET | ||
| 207 | + # tokenUrl: https://auth.context7.com/token | ||
| 208 | + headers: # Optional additional headers | ||
| 209 | + X-Custom-Header: value | ||
| 210 | + timeout: 30s # Per-request timeout | ||
| 211 | + | ||
| 212 | + - name: local-git | ||
| 213 | + command: /usr/local/bin/mcp-git | ||
| 214 | + transport: stdio | ||
| 215 | + args: ["--stdio"] | ||
| 216 | + env: # Optional env vars for stdio | ||
| 217 | + GIT_TOKEN: "{{.env.GIT_TOKEN}}" | ||
| 218 | + ``` | ||
| 219 | + | ||
| 220 | + **Key design decisions:** | ||
| 221 | + - `name` is the server alias, used to prefix all its tools (e.g., `context7_list_resources`) | ||
| 222 | + - `url` for HTTP, `command` for stdio — mutually exclusive | ||
| 223 | + - Auth config supports multiple modes, extensible for future needs | ||
| 224 | + - Timeout at discovery (startup) prevents hanging the agent | ||
| 225 | + - Per-server timeout for individual tool calls | ||
| 226 | + | ||
| 227 | + ### Tool Wrapping Strategy | ||
| 228 | + | ||
| 229 | + For each discovered MCP tool, create a wrapped Genkit tool: | ||
| 230 | + | ||
| 231 | + The wrapper follows the `lspEvented` skeleton: sink branch → ToolStart → Allow → ToolRunning → run → ToolEnd → `record(d, name, title, truncate(out))`. | ||
| 232 | + | ||
| 233 | + **Contract compliance:** | ||
| 234 | + - ✅ `Allow()` called via event system (kind="execute" by default) | ||
| 235 | + - ✅ `record()` for loop detection | ||
| 236 | + - ✅ `truncate()` on output | ||
| 237 | + - ✅ Errors returned as TEXT | ||
| 238 | + - ✅ `echoOutput()` for terminal visibility | ||
| 239 | + - ✅ Context timeout inherited from turn context | ||
| 240 | + | ||
| 241 | + **Name collision handling:** | ||
| 242 | + - Prefix each MCP tool with server alias: `{serverName}_{originalToolName}` | ||
| 243 | + - Reject any tool that would collide with built-in tools (bash, read_file, write_file, edit_file, lsp, read_skill, plan) | ||
| 244 | + - Warn on collisions between MCP servers | ||
| 245 | + | ||
| 246 | + ### Discovery at Startup | ||
| 247 | + | ||
| 248 | + In `main.go`, add `assembleMCP()` alongside `assembleLSP()`: | ||
| 249 | + - Runs BEFORE banner display | ||
| 250 | + - Has a configurable timeout (default 5s) | ||
| 251 | + - On failure: logs warning, continues without MCP tools | ||
| 252 | + - Validates no name collisions BEFORE declaring tools | ||
| 253 | + - Chains close functions to properly clean up connections | ||
| 254 | + | ||
| 255 | + **Critical difference from LSP:** MCP discovery is NOT lazy — tool list is baked into the prompt at startup. This means: | ||
| 256 | + - Startup latency increases with each configured MCP server | ||
| 257 | + - Tool list is static for the session | ||
| 258 | + - If a stdio server dies mid-session, tools become unavailable (no auto-restart) | ||
| 259 | + | ||
| 260 | + ### HTTP Client Implementation | ||
| 261 | + | ||
| 262 | + For streamable HTTP (SSE/JSON-RPC): | ||
| 263 | + - Implements MCP protocol over HTTP with SSE for streaming | ||
| 264 | + - Handles authentication headers | ||
| 265 | + - Manages connection pooling and timeouts | ||
| 266 | + - Parses tool descriptions and schemas | ||
| 267 | + | ||
| 268 | + ### Stdio Client Implementation | ||
| 269 | + | ||
| 270 | + For stdio (JSON-RPC 2.0 over stdin/stdout): | ||
| 271 | + - Launches process on first use OR at startup (TBD based on discovery strategy) | ||
| 272 | + - Manages process lifecycle (start, stop, monitor stderr) | ||
| 273 | + - Implements JSON-RPC 2.0 framing | ||
| 274 | + - Handles process death and restarts (if discovery is lazy) | ||
| 275 | + | ||
| 276 | + **Recommendation:** Start stdio processes at discovery time (not lazy) to keep tool list static. This matches the HTTP behavior and simplifies the model. | ||
| 277 | + | ||
| 278 | + ### Authentication Handling | ||
| 279 | + | ||
| 280 | + Support for: | ||
| 281 | + - **None**: No authentication | ||
| 282 | + - **Bearer token**: Simple token from environment variable | ||
| 283 | + - **API Key**: Key in custom header (configurable header name) | ||
| 284 | + - **OAuth2 Client Credentials**: Fetch token from auth server, cache it | ||
| 285 | + | ||
| 286 | + Token caching: | ||
| 287 | + - Cache OAuth2 tokens in memory (per session) | ||
| 288 | + - Cache location: `~/.mm/mcp-tokens/` with server-specific files | ||
| 289 | + - Cache expiration: respect token expiry, refresh proactively | ||
| 290 | + | ||
| 291 | + ### Dependencies | ||
| 292 | + | ||
| 293 | + **Approach: Optional dependency via import blank** | ||
| 294 | + | ||
| 295 | + To avoid forcing `mcp-go` on all users, but still allow it when MCP is enabled: | ||
| 296 | + | ||
| 297 | + ```go | ||
| 298 | + // internal/mcp/import.go | ||
| 299 | + package mcp | ||
| 300 | + | ||
| 301 | + import ( | ||
| 302 | + _ "github.com/mark3labs/mcp-go/mcp" | ||
| 303 | + ) | ||
| 304 | + ``` | ||
| 305 | + | ||
| 306 | + This adds the dependency to `go.sum` only when the package is imported. Since MCP is disabled by default and `internal/mcp` is only imported when `mcpEnabled: true`, users without MCP don't pay the dependency cost. | ||
| 307 | + | ||
| 308 | + **Alternative considered and rejected:** Build tags. They complicate the build process and make it harder to distribute a single binary. | ||
| 309 | + | ||
| 310 | + ### Error Handling & Recovery | ||
| 311 | + | ||
| 312 | + 1. **Discovery failure** → warning in banner, MCP tools not declared | ||
| 313 | + 2. **Server connection fails during call** → return error as TEXT, record in detector | ||
| 314 | + 3. **Server dies mid-session** → subsequent calls fail with connection error | ||
| 315 | + 4. **Timeout** → return timeout error as TEXT | ||
| 316 | + 5. **Invalid tool name** → return descriptive error | ||
| 317 | + 6. **Permission denied** → return user-friendly rejection message | ||
| 318 | + | ||
| 319 | + **No automatic restart** of stdio servers — this would change the tool list mid-session. If a stdio server dies, the user must restart mm. This is a deliberate trade-off: prompt stability > resilience. | ||
| 320 | + | ||
| 321 | + ### Testing Strategy | ||
| 322 | + | ||
| 323 | + 1. Unit tests with mocked MCP servers (in-memory implementations) | ||
| 324 | + 2. Integration tests with a simple test MCP server | ||
| 325 | + 3. Test both transport types (stdio and HTTP) | ||
| 326 | + 4. Test all auth modes | ||
| 327 | + 5. Test name collision detection and prevention | ||
| 328 | + 6. Test timeout and error handling | ||
| 329 | + 7. Test that MCP disabled = zero overhead (no imports, no init) | ||
| 330 | + 8. Measure prompt cost with various numbers of MCP tools | ||
| 331 | + | ||
| 332 | + ### Rollout Plan | ||
| 333 | + | ||
| 334 | + 1. **Phase 1:** Implement core infrastructure (config, basic HTTP client, wrapping) — no default enablement | ||
| 335 | + 2. **Phase 2:** Add stdio support | ||
| 336 | + 3. **Phase 3:** Add OAuth2 support | ||
| 337 | + 4. **Phase 4:** Test with public MCP servers (context7.com, etc.) | ||
| 338 | + 5. **Phase 5:** Measure prompt cost impact with the tool-count experiment (6 → 16 → 26 tools) | ||
| 339 | + 6. **Phase 6:** If impact is acceptable, document and release as experimental | ||
| 340 | + 7. **Phase 7:** Gather feedback, iterate | ||
| 341 | + | ||
| 342 | + ### Open Questions | ||
| 343 | + | ||
| 344 | + 1. **Should we support lazy discovery for stdio servers?** This would allow on-demand starting but complicates tool list management. Current proposal: discover all at startup. | ||
| 345 | + | ||
| 346 | + 2. **Should we cache tool descriptions across sessions?** This would reduce startup time but requires persistent storage and cache invalidation. | ||
| 347 | + | ||
| 348 | + 3. **How to handle servers that expose many tools (100+)?** This could significantly impact prompt cost. Consider: configurable max tools per server, or a tool grouping mechanism. | ||
| 349 | + | ||
| 350 | + 4. **Should we support filtering tools by name/pattern?** Allow users to select which tools from a server to expose. | ||
| 351 | + | ||
| 352 | + 5. **How to handle binary/non-JSON tool inputs/outputs?** Some MCP tools might work with binary data (e.g., image processing). Current proposal: text-only, reject binary tools with a warning. | ||
| 353 | + | ||
| 354 | + 6. **Should we expose server info to the model?** E.g., tool descriptions could include "[from context7 server]" to help the model choose. | ||
| 355 | + | ||
| 356 | + ### Cost/Benefit Analysis | ||
| 357 | + | ||
| 358 | + **Costs:** | ||
| 359 | + - ~400-600 lines of new code (config + client + wrapping + integration) | ||
| 360 | + - New dependency (`mcp-go`) — but only imported when MCP is enabled | ||
| 361 | + - Prompt cost: N MCP tools added to every request forever | ||
| 362 | + - Discovery latency at startup (bounded by timeout) | ||
| 363 | + - Maintenance burden for a new subsystem | ||
| 364 | + - Complexity: MCP is fundamentally more complex than CLI or LSP | ||
| 365 | + | ||
| 366 | + **Benefits:** | ||
| 367 | + - Access to remote-only capabilities (context7.com, browser tools, etc.) | ||
| 368 | + - Better UX for `bashTool: false` configurations | ||
| 369 | + - Per-tool permissions (granular control) | ||
| 370 | + - Future-proofing for MCP ecosystem growth | ||
| 371 | + - Solves the two cases where CLI is not enough (from original analysis) | ||
| 372 | + | ||
| 373 | + **Risks:** | ||
| 374 | + - Prompt cost could degrade model performance significantly (the experiment from original analysis is CRITICAL) | ||
| 375 | + - MCP server reliability issues affect mm reliability | ||
| 376 | + - Security: wrapping must be perfect, any gap exposes mm to the security issues described in the original analysis | ||
| 377 | + - Complexity: more code = more bugs | ||
| 378 | + | ||
| 379 | + **Verdict:** The costs and risks are non-trivial but bounded and manageable. The benefits are real but apply to a narrow set of use cases. **Implementation is justified ONLY for users who specifically need remote-only MCP servers and understand the costs.** The disabled-by-default design ensures zero cost for users who don't need it. The tool-count experiment from the original analysis MUST be run before considering this for wider use. | ||
| 380 | + | ||
| 381 | + ### Comparison with Alternatives | ||
| 382 | + | ||
| 383 | + | Approach | Pros | Cons | | ||
| 384 | + |---|---|---| | ||
| 385 | + | **This proposal** | Full integration, respect contracts, flexible | Complex, prompt cost, maintenance | | ||
| 386 | + | **External MCP client CLI** | Zero mm changes, already works | Loses all mm tooling (permissions, truncation, etc.) | | ||
| 387 | + | **Raw GetActiveTools splice** | Simple, ~10 lines | VIOLATES ALL SECURITY CONTRACTS — NEVER SHIP | | ||
| 388 | + | **Skills as MCP equivalent** | Already works, no new code | Manual, not dynamic, no remote capabilities | | ||
| 389 | + | **Separate mm-mcp binary** | Isolated, optional | Duplicate code, maintenance burden | | ||
| 390 | + | ||
| 391 | + **Conclusion:** This proposal is the only viable approach that provides MCP support while maintaining mm's security and usability guarantees. | ||
| 392 | +comments: [] | ||
added
.tickets/issues/0040-better-list-of-detected-tools-at-the-start-of-mi.yaml +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 40 | |
| 3 | +title: Better list of detected tools at the start of mini me | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-09-23T10:28:36.392Z | |
| 9 | +updatedAt: 2026-09-23T10:28:36.392Z | |
| 10 | +labels: [] | |
| 11 | +body: "" | |
| 12 | +tasks: [] | |
| 13 | +comments: [] | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 40 | ||
| 3 | +title: Better list of detected tools at the start of mini me | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-09-23T10:28:36.392Z | ||
| 9 | +updatedAt: 2026-09-23T10:28:36.392Z | ||
| 10 | +labels: [] | ||
| 11 | +body: "" | ||
| 12 | +tasks: [] | ||
| 13 | +comments: [] | ||
added
.vibe/skills/issuespec/SKILL.md +493 -0 | new file mode 100644 | ||
| @@ -0,0 +1,493 @@ | ||
| 1 | +--- | |
| 2 | +name: issuespec | |
| 3 | +description: Create and modify IssueSpec tickets (issues, labels, milestones, epics) in the repository. Use when you need to manage the .tickets/ directory structure and files that follow the IssueSpec format. | |
| 4 | +--- | |
| 5 | +# IssueSpec Ticket Management Skill | |
| 6 | + | |
| 7 | +## Description | |
| 8 | + | |
| 9 | +This skill provides tools to create, read, update, and delete IssueSpec tickets and catalogue entries (labels, milestones, epics) directly from the repository. It understands the [IssueSpec format](https://codeberg.org/k33g/issuespec) and ensures all files remain valid. | |
| 10 | + | |
| 11 | +## When to Use | |
| 12 | + | |
| 13 | +- When you need to create a new issue, label, milestone, or epic | |
| 14 | +- When you need to update an existing ticket (change state, add comments, modify tasks) | |
| 15 | +- When you need to link issues together (related issues) | |
| 16 | +- When you need to validate ticket files | |
| 17 | +- When you need to search or filter the ticket store | |
| 18 | + | |
| 19 | +## issueSpec file structure | |
| 20 | + | |
| 21 | +``` | |
| 22 | +.tickets/ | |
| 23 | +├── config.yaml # Schema version | |
| 24 | +├── labels.yaml # Label catalogue | |
| 25 | +├── milestones.yaml # Milestone catalogue | |
| 26 | +├── epics.yaml # Epic catalogue | |
| 27 | +└── issues/ | |
| 28 | + ├── 0001-issue-title.yaml | |
| 29 | + └── 0002-another-issue.yaml | |
| 30 | +``` | |
| 31 | + | |
| 32 | +## Understanding the Format | |
| 33 | + | |
| 34 | +Based on the IssueSpec documentation: | |
| 35 | + | |
| 36 | +### Ticket File (issues/NNNN-slug.yaml) | |
| 37 | + | |
| 38 | +| Field | Type | Required | Default | Description | | |
| 39 | +| --- | --- | --- | --- | --- | | |
| 40 | +| `id` | integer >= 1 | yes | — | Issue number, unique, never reused | | |
| 41 | +| `title` | non-empty string | yes | — | One line; whitespace collapsed on write | | |
| 42 | +| `state` | `open` \| `closed` | yes | — | Lifecycle state | | |
| 43 | +| `author` | mapping | yes | — | Who filed it (name + email from git config) | | |
| 44 | +| `createdAt` | timestamp | yes | — | ISO 8601, UTC with milliseconds | | |
| 45 | +| `updatedAt` | timestamp | yes | — | Last content change | | |
| 46 | +| `labels` | list of strings | no | `[]` | Label names from labels.yaml | | |
| 47 | +| `milestone` | string | no | absent | Milestone title from milestones.yaml | | |
| 48 | +| `epic` | string | no | absent | Epic title from epics.yaml | | |
| 49 | +| `related` | list of integers | no | `[]` | Ids of related issues (one-side storage) | | |
| 50 | +| `body` | string | no | `""` | Description as Markdown (literal block scalar) | | |
| 51 | +| `tasks` | list of mappings | no | `[]` | Checklist items | | |
| 52 | +| `comments` | list of mappings | no | `[]` | Discussion entries | | |
| 53 | + | |
| 54 | +### Author Format (for issues and comments) | |
| 55 | + | |
| 56 | +```yaml | |
| 57 | +author: | |
| 58 | + name: string | |
| 59 | + email: string | |
| 60 | +``` | |
| 61 | + | |
| 62 | +### Task Format | |
| 63 | + | |
| 64 | +| Field | Type | Required | Default | | |
| 65 | +| --- | --- | --- | --- | | |
| 66 | +| `id` | integer >= 1 | yes | — | Unique within its issue | | |
| 67 | +| `title` | non-empty string | yes | — | One line | | |
| 68 | +| `state` | `open` \| `closed` | yes | — | Lifecycle state | | |
| 69 | +| `priority` | `none` \| `low` \| `medium` \| `high` \| `urgent` | no | `none` | Urgency | | |
| 70 | +| `author` | mapping | yes | — | Same shape as issue author | | |
| 71 | +| `createdAt` | timestamp | yes | — | When added | | |
| 72 | +| `updatedAt` | timestamp | no | absent | Last rename/reprioritise | | |
| 73 | +| `closedAt` | timestamp | no | absent | When closed | | |
| 74 | + | |
| 75 | +### Comment Format | |
| 76 | + | |
| 77 | +| Field | Type | Required | Default | | |
| 78 | +| --- | --- | --- | --- | | |
| 79 | +| `id` | integer >= 1 | yes | — | Unique within its issue | | |
| 80 | +| `author` | mapping | yes | — | Same shape as issue author | | |
| 81 | +| `createdAt` | timestamp | yes | — | When written | | |
| 82 | +| `updatedAt` | timestamp | no | absent | When last edited | | |
| 83 | +| `body` | string | no | `""` | Comment text as Markdown | | |
| 84 | + | |
| 85 | +### Catalogue Files | |
| 86 | + | |
| 87 | +#### labels.yaml | |
| 88 | + | |
| 89 | +```yaml | |
| 90 | +labels: | |
| 91 | + - name: bug | |
| 92 | + color: "#d73a4a" | |
| 93 | + description: Something is not working | |
| 94 | + - name: priority::high | |
| 95 | + color: "#d93f0b" | |
| 96 | + description: Should be picked up next | |
| 97 | +``` | |
| 98 | + | |
| 99 | +#### milestones.yaml | |
| 100 | + | |
| 101 | +```yaml | |
| 102 | +milestones: | |
| 103 | + - title: v1.0 | |
| 104 | + state: open | |
| 105 | + description: First public release | |
| 106 | + dueOn: 2026-09-30 | |
| 107 | +``` | |
| 108 | + | |
| 109 | +#### epics.yaml | |
| 110 | + | |
| 111 | +```yaml | |
| 112 | +epics: | |
| 113 | + - title: Authentication | |
| 114 | + state: open | |
| 115 | + color: "#5319e7" | |
| 116 | + description: Everything about signing in | |
| 117 | +``` | |
| 118 | + | |
| 119 | +## Skill Workflow | |
| 120 | + | |
| 121 | +### Phase 1: Understand the Current State | |
| 122 | + | |
| 123 | +1. **Read the ticket store**: List all files in `.tickets/` directory | |
| 124 | +2. **Identify the operation**: Determine if you need to create, update, or delete | |
| 125 | +3. **Validate existing files**: Ensure all YAML files are valid IssueSpec format | |
| 126 | + | |
| 127 | +### Phase 2: Prepare the Data | |
| 128 | + | |
| 129 | +1. **Gather required fields** based on the operation type | |
| 130 | +2. **Validate field values**: Ensure all values conform to IssueSpec requirements | |
| 131 | +3. **Generate proper timestamps**: Use ISO 8601 format with milliseconds | |
| 132 | +4. **Determine author**: Use git config user.name and user.email | |
| 133 | + | |
| 134 | +### Phase 3: Generate/Update the File | |
| 135 | + | |
| 136 | +1. **Use proper indentation**: 2 spaces for all YAML (IssueSpec standard) | |
| 137 | +2. **Format body correctly**: Use literal block scalar (`|`) for multi-line body | |
| 138 | +3. **Maintain header**: Include `# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.` | |
| 139 | +4. **Respect file naming**: `NNNN-slug.yaml` where NNNN is zero-padded id | |
| 140 | + | |
| 141 | +### Phase 4: Validate the Result | |
| 142 | + | |
| 143 | +1. **Check YAML syntax**: Ensure the file is valid YAML | |
| 144 | +2. **Validate schema**: Ensure all required fields are present and valid | |
| 145 | +3. **Check references**: Ensure labels, milestones, epics exist in catalogues | |
| 146 | +4. **Verify uniqueness**: Ensure ids are unique within their scope | |
| 147 | + | |
| 148 | +## Operations | |
| 149 | + | |
| 150 | +### Create a New Issue | |
| 151 | + | |
| 152 | +**Steps:** | |
| 153 | +1. Read existing issues to find the next available id | |
| 154 | +2. Generate slug from title (lowercase, replace non-alphanumeric with `-`, max 48 chars) | |
| 155 | +3. Create file: `.tickets/issues/NNNN-slug.yaml` | |
| 156 | +4. Populate required fields: id, title, state, author, createdAt, updatedAt | |
| 157 | +5. Add optional fields: body, labels, milestone, epic, related, tasks, comments | |
| 158 | + | |
| 159 | +**Example:** | |
| 160 | +```yaml | |
| 161 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 162 | +id: 42 | |
| 163 | +title: Add OAuth2 support to MCP client | |
| 164 | +state: open | |
| 165 | +author: | |
| 166 | + name: Developer Name | |
| 167 | + email: developer@example.com | |
| 168 | +createdAt: 2026-09-23T22:00:00.000Z | |
| 169 | +updatedAt: 2026-09-23T22:00:00.000Z | |
| 170 | +labels: | |
| 171 | + - kind::feature | |
| 172 | + - priority::high | |
| 173 | +milestone: v1.0 | |
| 174 | +epic: MCP Integration | |
| 175 | +body: | | |
| 176 | + We need to add OAuth2 client credentials authentication for MCP servers | |
| 177 | + that require it, such as context7.com. | |
| 178 | + | |
| 179 | + This involves implementing the OAuth2 flow in the HTTP client. | |
| 180 | +tasks: | |
| 181 | + - id: 1 | |
| 182 | + title: Research OAuth2 requirements for context7.com | |
| 183 | + state: open | |
| 184 | + priority: high | |
| 185 | + author: | |
| 186 | + name: Developer Name | |
| 187 | + email: developer@example.com | |
| 188 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 189 | +comments: [] | |
| 190 | +``` | |
| 191 | + | |
| 192 | +### Update an Existing Issue | |
| 193 | + | |
| 194 | +**Steps:** | |
| 195 | +1. Read the existing issue file | |
| 196 | +2. Update the specified fields | |
| 197 | +3. Update `updatedAt` timestamp | |
| 198 | +4. If state changes to closed, consider adding `closedAt` (not in IssueSpec, but can be added) | |
| 199 | +5. If adding tasks/comments, ensure ids are unique within the issue | |
| 200 | + | |
| 201 | +**Note:** Only the author of a comment can edit or delete it through IssueSpec. | |
| 202 | + | |
| 203 | +### Add a Comment to an Issue | |
| 204 | + | |
| 205 | +**Steps:** | |
| 206 | +1. Read the existing issue file | |
| 207 | +2. Find the highest existing comment id | |
| 208 | +3. Create new comment with next id | |
| 209 | +4. Set author from current git config | |
| 210 | +5. Set createdAt and updatedAt to current time | |
| 211 | +6. Add comment to comments list | |
| 212 | +7. Update issue's updatedAt | |
| 213 | + | |
| 214 | +**Example comment:** | |
| 215 | +```yaml | |
| 216 | +- id: 2 | |
| 217 | + author: | |
| 218 | + name: Reviewer Name | |
| 219 | + email: reviewer@example.com | |
| 220 | + createdAt: 2026-09-24T10:00:00.000Z | |
| 221 | + body: | | |
| 222 | + This looks good. One question: how do we handle token expiration? | |
| 223 | +``` | |
| 224 | + | |
| 225 | +### Add a Task to an Issue | |
| 226 | + | |
| 227 | +**Steps:** | |
| 228 | +1. Read the existing issue file | |
| 229 | +2. Find the highest existing task id | |
| 230 | +3. Create new task with next id | |
| 231 | +4. Set author (can be anyone, unlike comments) | |
| 232 | +5. Set createdAt to current time | |
| 233 | +6. Add task to tasks list | |
| 234 | +7. Update issue's updatedAt | |
| 235 | + | |
| 236 | +**Example task:** | |
| 237 | +```yaml | |
| 238 | +- id: 3 | |
| 239 | + title: Implement token caching | |
| 240 | + state: open | |
| 241 | + priority: medium | |
| 242 | + author: | |
| 243 | + name: Developer Name | |
| 244 | + email: developer@example.com | |
| 245 | + createdAt: 2026-09-24T11:00:00.000Z | |
| 246 | +``` | |
| 247 | + | |
| 248 | +### Create/Update a Label | |
| 249 | + | |
| 250 | +**Steps:** | |
| 251 | +1. Read existing labels.yaml | |
| 252 | +2. If creating: add new label to labels list | |
| 253 | +3. If updating: find label by name and update fields | |
| 254 | +4. Ensure name is unique | |
| 255 | +5. Ensure color is valid hex format (#rrggbb) | |
| 256 | + | |
| 257 | +**Example:** | |
| 258 | +```yaml | |
| 259 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 260 | +labels: | |
| 261 | + - name: mcp | |
| 262 | + color: "#7b68ee" | |
| 263 | + description: MCP-related features and integration | |
| 264 | +``` | |
| 265 | + | |
| 266 | +### Create/Update a Milestone | |
| 267 | + | |
| 268 | +**Steps:** | |
| 269 | +1. Read existing milestones.yaml | |
| 270 | +2. If creating: add new milestone to milestones list | |
| 271 | +3. If updating: find milestone by title and update fields | |
| 272 | +4. Ensure title is unique | |
| 273 | +5. Validate dueOn format (YYYY-MM-DD) | |
| 274 | + | |
| 275 | +### Create/Update an Epic | |
| 276 | + | |
| 277 | +**Steps:** | |
| 278 | +1. Read existing epics.yaml | |
| 279 | +2. If creating: add new epic to epics list | |
| 280 | +3. If updating: find epic by title and update fields | |
| 281 | +4. Ensure title is unique | |
| 282 | +5. Ensure color is valid hex format (#rrggbb) | |
| 283 | + | |
| 284 | +### Link Related Issues | |
| 285 | + | |
| 286 | +**Steps:** | |
| 287 | +1. Read both issue files | |
| 288 | +2. Add each issue's id to the other's related list (one-side storage) | |
| 289 | +3. Update both files' updatedAt | |
| 290 | + | |
| 291 | +**Note:** The relation is symmetric but stored on ONE side only. IssueSpec handles the symmetry when displaying. | |
| 292 | + | |
| 293 | +## File Naming Rules | |
| 294 | + | |
| 295 | +| Rule | Detail | | |
| 296 | +| --- | --- | | |
| 297 | +| Pattern | `<id>-<slug>.yaml`, where `<id>` is padded to at least four digits | | |
| 298 | +| Slug | Title, accents folded to ASCII, lowercased, every run of non-alphanumeric characters replaced by `-`, trimmed to 48 characters | | |
| 299 | +| Slug fallback | `issue`, when the title yields no usable characters | | |
| 300 | +| Authority | The **id** identifies the issue. A file whose slug no longer matches its title is still found and read. | | |
| 301 | +| Renaming | Changing a title rewrites the file under the new slug and deletes the old file | | |
| 302 | + | |
| 303 | +## Timestamp Generation | |
| 304 | + | |
| 305 | +Always use UTC time in ISO 8601 format with milliseconds: | |
| 306 | +- Format: `YYYY-MM-DDTHH:MM:SS.sssZ` | |
| 307 | +- Example: `2026-09-23T22:00:00.000Z` | |
| 308 | +- Always end with `Z` for UTC | |
| 309 | + | |
| 310 | +## Validation Rules | |
| 311 | + | |
| 312 | +### Required Fields | |
| 313 | +- Issue: id, title, state, author, createdAt, updatedAt | |
| 314 | +- Task: id, title, state, author, createdAt | |
| 315 | +- Comment: id, author, createdAt | |
| 316 | +- Label: name, color | |
| 317 | +- Milestone: title, state | |
| 318 | +- Epic: title, state | |
| 319 | + | |
| 320 | +### Field Constraints | |
| 321 | +- id: integer >= 1 | |
| 322 | +- title: non-empty string | |
| 323 | +- state: `open` or `closed` | |
| 324 | +- name (label): non-empty string, unique | |
| 325 | +- color: hex format `#rrggbb`, lowercased | |
| 326 | +- priority: `none`, `low`, `medium`, `high`, `urgent` | |
| 327 | +- dueOn: `YYYY-MM-DD` format | |
| 328 | + | |
| 329 | +### Cross-Reference Validation | |
| 330 | +- Labels in an issue must exist in labels.yaml | |
| 331 | +- Milestone in an issue must exist in milestones.yaml | |
| 332 | +- Epic in an issue must exist in epics.yaml | |
| 333 | +- Related issue ids must correspond to existing issues | |
| 334 | + | |
| 335 | +## Strict Formatting Rules | |
| 336 | + | |
| 337 | +**You must NEVER:** | |
| 338 | +- Use tabs for indentation (always 2 spaces) | |
| 339 | +- Hard-wrap prose in body or comments (one logical line per paragraph) | |
| 340 | +- Include the `body` field when it would be empty (omit it entirely) | |
| 341 | +- Use empty strings for absent fields (use YAML absent/omitted) | |
| 342 | +- Change the file header line | |
| 343 | + | |
| 344 | +**You must ALWAYS:** | |
| 345 | +- Use 2-space indentation throughout | |
| 346 | +- Use literal block scalar (`|`) for multi-line body and comment body | |
| 347 | +- Indent literal block content by 2 additional spaces | |
| 348 | +- Keep the header line as the first line | |
| 349 | +- Use lowercased hex colors | |
| 350 | +- Ensure all timestamps are in UTC with milliseconds | |
| 351 | + | |
| 352 | +## Common Pitfalls | |
| 353 | + | |
| 354 | +1. **Body formatting**: Multi-line body must use `|` and proper indentation | |
| 355 | +2. **Author consistency**: Use git config values, not hardcoded values | |
| 356 | +3. **Timestamp consistency**: All timestamps must be current when creating/updating | |
| 357 | +4. **Reference validation**: Always check that referenced labels/milestones/epics exist | |
| 358 | +5. **Id uniqueness**: Never reuse ids, even for deleted items | |
| 359 | +6. **File naming**: Slug must be properly generated from title | |
| 360 | + | |
| 361 | +## Examples | |
| 362 | + | |
| 363 | +### Minimal Valid Issue | |
| 364 | + | |
| 365 | +```yaml | |
| 366 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 367 | +id: 1 | |
| 368 | +title: Fix bug | |
| 369 | +author: | |
| 370 | + name: k33g | |
| 371 | + email: ph.charriere@gmail.com | |
| 372 | +state: open | |
| 373 | +createdAt: 2026-09-23T22:00:00.000Z | |
| 374 | +updatedAt: 2026-09-23T22:00:00.000Z | |
| 375 | +labels: [] | |
| 376 | +comments: [] | |
| 377 | +``` | |
| 378 | + | |
| 379 | +### Issue with All Fields | |
| 380 | + | |
| 381 | +```yaml | |
| 382 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 383 | +id: 2 | |
| 384 | +title: Implement MCP Client Support | |
| 385 | +state: open | |
| 386 | +author: | |
| 387 | + name: k33g | |
| 388 | + email: ph.charriere@gmail.com | |
| 389 | +createdAt: 2026-09-23T22:00:00.000Z | |
| 390 | +updatedAt: 2026-09-24T10:00:00.000Z | |
| 391 | +labels: | |
| 392 | + - kind::feature | |
| 393 | + - priority::high | |
| 394 | +milestone: v1.0 | |
| 395 | +epic: MCP Integration | |
| 396 | +related: | |
| 397 | + - 1 | |
| 398 | +body: | | |
| 399 | + This issue tracks the implementation of MCP client support in mini-me. | |
| 400 | + | |
| 401 | + ## Requirements | |
| 402 | + | |
| 403 | + - Support stdio transport | |
| 404 | + - Support HTTP streamable transport | |
| 405 | + - Handle authentication (bearer, API key, OAuth2) | |
| 406 | + | |
| 407 | + ## Design | |
| 408 | + | |
| 409 | + See the proposal in issue #4. | |
| 410 | +tasks: | |
| 411 | + - id: 1 | |
| 412 | + title: Design configuration schema | |
| 413 | + state: closed | |
| 414 | + priority: high | |
| 415 | + author: | |
| 416 | + name: k33g | |
| 417 | + email: ph.charriere@gmail.com | |
| 418 | + createdAt: 2026-09-23T22:00:00.000Z | |
| 419 | + closedAt: 2026-09-23T23:00:00.000Z | |
| 420 | + - id: 2 | |
| 421 | + title: Implement HTTP client | |
| 422 | + state: open | |
| 423 | + priority: high | |
| 424 | + author: | |
| 425 | + name: k33g | |
| 426 | + email: ph.charriere@gmail.com | |
| 427 | + createdAt: 2026-09-24T09:00:00.000Z | |
| 428 | +comments: | |
| 429 | + - id: 1 | |
| 430 | + author: | |
| 431 | + name: reviewer | |
| 432 | + email: reviewer@example.com | |
| 433 | + createdAt: 2026-09-24T08:00:00.000Z | |
| 434 | + body: | | |
| 435 | + This looks comprehensive. Have you considered the prompt cost impact? | |
| 436 | +``` | |
| 437 | + | |
| 438 | +## Helper Functions | |
| 439 | + | |
| 440 | +When implementing this skill, use these helper patterns: | |
| 441 | + | |
| 442 | +### Generate Slug from Title | |
| 443 | + | |
| 444 | +```go | |
| 445 | +func generateSlug(title string) string { | |
| 446 | + // Lowercase | |
| 447 | + // Replace accents with ASCII equivalents | |
| 448 | + // Replace non-alphanumeric runs with single '-' | |
| 449 | + // Trim to 48 characters | |
| 450 | + // If empty, return "issue" | |
| 451 | +} | |
| 452 | +``` | |
| 453 | + | |
| 454 | +### Get Next Id | |
| 455 | + | |
| 456 | +```go | |
| 457 | +func getNextId(files []string) int { | |
| 458 | + // Extract ids from filenames (NNNN-*.yaml) | |
| 459 | + // Find maximum id | |
| 460 | + // Return max + 1 | |
| 461 | +} | |
| 462 | +``` | |
| 463 | + | |
| 464 | +### Get Git Author | |
| 465 | + | |
| 466 | +```bash | |
| 467 | +git config user.name | |
| 468 | +git config user.email | |
| 469 | +``` | |
| 470 | + | |
| 471 | +### Get Current Timestamp | |
| 472 | + | |
| 473 | +```go | |
| 474 | +func getTimestamp() string { | |
| 475 | + return time.Now().UTC().Format("2006-01-02T15:04:05.000Z") | |
| 476 | +} | |
| 477 | +``` | |
| 478 | + | |
| 479 | +### Validate YAML | |
| 480 | + | |
| 481 | +Ensure the generated YAML: | |
| 482 | +1. Can be parsed by a YAML parser | |
| 483 | +2. Has all required fields | |
| 484 | +3. Has valid field values | |
| 485 | +4. Has proper indentation (2 spaces) | |
| 486 | + | |
| 487 | +## Final Notes | |
| 488 | + | |
| 489 | +This skill is designed to work alongside the IssueSpec VS Code extension. Files created or modified by this skill should be indistinguishable from those created through the extension's UI. Always prioritize correctness and validation over convenience. | |
| 490 | + | |
| 491 | +For more information, see: | |
| 492 | +- [IssueSpec Documentation](https://codeberg.org/k33g/issuespec) | |
| 493 | +- [Ticket File Format](https://codeberg.org/k33g/issuespec/src/branch/main/docs/en/reference/ticket-files.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,493 @@ | |||
| 1 | +--- | ||
| 2 | +name: issuespec | ||
| 3 | +description: Create and modify IssueSpec tickets (issues, labels, milestones, epics) in the repository. Use when you need to manage the .tickets/ directory structure and files that follow the IssueSpec format. | ||
| 4 | +--- | ||
| 5 | +# IssueSpec Ticket Management Skill | ||
| 6 | + | ||
| 7 | +## Description | ||
| 8 | + | ||
| 9 | +This skill provides tools to create, read, update, and delete IssueSpec tickets and catalogue entries (labels, milestones, epics) directly from the repository. It understands the [IssueSpec format](https://codeberg.org/k33g/issuespec) and ensures all files remain valid. | ||
| 10 | + | ||
| 11 | +## When to Use | ||
| 12 | + | ||
| 13 | +- When you need to create a new issue, label, milestone, or epic | ||
| 14 | +- When you need to update an existing ticket (change state, add comments, modify tasks) | ||
| 15 | +- When you need to link issues together (related issues) | ||
| 16 | +- When you need to validate ticket files | ||
| 17 | +- When you need to search or filter the ticket store | ||
| 18 | + | ||
| 19 | +## issueSpec file structure | ||
| 20 | + | ||
| 21 | +``` | ||
| 22 | +.tickets/ | ||
| 23 | +├── config.yaml # Schema version | ||
| 24 | +├── labels.yaml # Label catalogue | ||
| 25 | +├── milestones.yaml # Milestone catalogue | ||
| 26 | +├── epics.yaml # Epic catalogue | ||
| 27 | +└── issues/ | ||
| 28 | + ├── 0001-issue-title.yaml | ||
| 29 | + └── 0002-another-issue.yaml | ||
| 30 | +``` | ||
| 31 | + | ||
| 32 | +## Understanding the Format | ||
| 33 | + | ||
| 34 | +Based on the IssueSpec documentation: | ||
| 35 | + | ||
| 36 | +### Ticket File (issues/NNNN-slug.yaml) | ||
| 37 | + | ||
| 38 | +| Field | Type | Required | Default | Description | | ||
| 39 | +| --- | --- | --- | --- | --- | | ||
| 40 | +| `id` | integer >= 1 | yes | — | Issue number, unique, never reused | | ||
| 41 | +| `title` | non-empty string | yes | — | One line; whitespace collapsed on write | | ||
| 42 | +| `state` | `open` \| `closed` | yes | — | Lifecycle state | | ||
| 43 | +| `author` | mapping | yes | — | Who filed it (name + email from git config) | | ||
| 44 | +| `createdAt` | timestamp | yes | — | ISO 8601, UTC with milliseconds | | ||
| 45 | +| `updatedAt` | timestamp | yes | — | Last content change | | ||
| 46 | +| `labels` | list of strings | no | `[]` | Label names from labels.yaml | | ||
| 47 | +| `milestone` | string | no | absent | Milestone title from milestones.yaml | | ||
| 48 | +| `epic` | string | no | absent | Epic title from epics.yaml | | ||
| 49 | +| `related` | list of integers | no | `[]` | Ids of related issues (one-side storage) | | ||
| 50 | +| `body` | string | no | `""` | Description as Markdown (literal block scalar) | | ||
| 51 | +| `tasks` | list of mappings | no | `[]` | Checklist items | | ||
| 52 | +| `comments` | list of mappings | no | `[]` | Discussion entries | | ||
| 53 | + | ||
| 54 | +### Author Format (for issues and comments) | ||
| 55 | + | ||
| 56 | +```yaml | ||
| 57 | +author: | ||
| 58 | + name: string | ||
| 59 | + email: string | ||
| 60 | +``` | ||
| 61 | + | ||
| 62 | +### Task Format | ||
| 63 | + | ||
| 64 | +| Field | Type | Required | Default | | ||
| 65 | +| --- | --- | --- | --- | | ||
| 66 | +| `id` | integer >= 1 | yes | — | Unique within its issue | | ||
| 67 | +| `title` | non-empty string | yes | — | One line | | ||
| 68 | +| `state` | `open` \| `closed` | yes | — | Lifecycle state | | ||
| 69 | +| `priority` | `none` \| `low` \| `medium` \| `high` \| `urgent` | no | `none` | Urgency | | ||
| 70 | +| `author` | mapping | yes | — | Same shape as issue author | | ||
| 71 | +| `createdAt` | timestamp | yes | — | When added | | ||
| 72 | +| `updatedAt` | timestamp | no | absent | Last rename/reprioritise | | ||
| 73 | +| `closedAt` | timestamp | no | absent | When closed | | ||
| 74 | + | ||
| 75 | +### Comment Format | ||
| 76 | + | ||
| 77 | +| Field | Type | Required | Default | | ||
| 78 | +| --- | --- | --- | --- | | ||
| 79 | +| `id` | integer >= 1 | yes | — | Unique within its issue | | ||
| 80 | +| `author` | mapping | yes | — | Same shape as issue author | | ||
| 81 | +| `createdAt` | timestamp | yes | — | When written | | ||
| 82 | +| `updatedAt` | timestamp | no | absent | When last edited | | ||
| 83 | +| `body` | string | no | `""` | Comment text as Markdown | | ||
| 84 | + | ||
| 85 | +### Catalogue Files | ||
| 86 | + | ||
| 87 | +#### labels.yaml | ||
| 88 | + | ||
| 89 | +```yaml | ||
| 90 | +labels: | ||
| 91 | + - name: bug | ||
| 92 | + color: "#d73a4a" | ||
| 93 | + description: Something is not working | ||
| 94 | + - name: priority::high | ||
| 95 | + color: "#d93f0b" | ||
| 96 | + description: Should be picked up next | ||
| 97 | +``` | ||
| 98 | + | ||
| 99 | +#### milestones.yaml | ||
| 100 | + | ||
| 101 | +```yaml | ||
| 102 | +milestones: | ||
| 103 | + - title: v1.0 | ||
| 104 | + state: open | ||
| 105 | + description: First public release | ||
| 106 | + dueOn: 2026-09-30 | ||
| 107 | +``` | ||
| 108 | + | ||
| 109 | +#### epics.yaml | ||
| 110 | + | ||
| 111 | +```yaml | ||
| 112 | +epics: | ||
| 113 | + - title: Authentication | ||
| 114 | + state: open | ||
| 115 | + color: "#5319e7" | ||
| 116 | + description: Everything about signing in | ||
| 117 | +``` | ||
| 118 | + | ||
| 119 | +## Skill Workflow | ||
| 120 | + | ||
| 121 | +### Phase 1: Understand the Current State | ||
| 122 | + | ||
| 123 | +1. **Read the ticket store**: List all files in `.tickets/` directory | ||
| 124 | +2. **Identify the operation**: Determine if you need to create, update, or delete | ||
| 125 | +3. **Validate existing files**: Ensure all YAML files are valid IssueSpec format | ||
| 126 | + | ||
| 127 | +### Phase 2: Prepare the Data | ||
| 128 | + | ||
| 129 | +1. **Gather required fields** based on the operation type | ||
| 130 | +2. **Validate field values**: Ensure all values conform to IssueSpec requirements | ||
| 131 | +3. **Generate proper timestamps**: Use ISO 8601 format with milliseconds | ||
| 132 | +4. **Determine author**: Use git config user.name and user.email | ||
| 133 | + | ||
| 134 | +### Phase 3: Generate/Update the File | ||
| 135 | + | ||
| 136 | +1. **Use proper indentation**: 2 spaces for all YAML (IssueSpec standard) | ||
| 137 | +2. **Format body correctly**: Use literal block scalar (`|`) for multi-line body | ||
| 138 | +3. **Maintain header**: Include `# Managed by IssueSpec. Hand edits are welcome; keep the schema valid.` | ||
| 139 | +4. **Respect file naming**: `NNNN-slug.yaml` where NNNN is zero-padded id | ||
| 140 | + | ||
| 141 | +### Phase 4: Validate the Result | ||
| 142 | + | ||
| 143 | +1. **Check YAML syntax**: Ensure the file is valid YAML | ||
| 144 | +2. **Validate schema**: Ensure all required fields are present and valid | ||
| 145 | +3. **Check references**: Ensure labels, milestones, epics exist in catalogues | ||
| 146 | +4. **Verify uniqueness**: Ensure ids are unique within their scope | ||
| 147 | + | ||
| 148 | +## Operations | ||
| 149 | + | ||
| 150 | +### Create a New Issue | ||
| 151 | + | ||
| 152 | +**Steps:** | ||
| 153 | +1. Read existing issues to find the next available id | ||
| 154 | +2. Generate slug from title (lowercase, replace non-alphanumeric with `-`, max 48 chars) | ||
| 155 | +3. Create file: `.tickets/issues/NNNN-slug.yaml` | ||
| 156 | +4. Populate required fields: id, title, state, author, createdAt, updatedAt | ||
| 157 | +5. Add optional fields: body, labels, milestone, epic, related, tasks, comments | ||
| 158 | + | ||
| 159 | +**Example:** | ||
| 160 | +```yaml | ||
| 161 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 162 | +id: 42 | ||
| 163 | +title: Add OAuth2 support to MCP client | ||
| 164 | +state: open | ||
| 165 | +author: | ||
| 166 | + name: Developer Name | ||
| 167 | + email: developer@example.com | ||
| 168 | +createdAt: 2026-09-23T22:00:00.000Z | ||
| 169 | +updatedAt: 2026-09-23T22:00:00.000Z | ||
| 170 | +labels: | ||
| 171 | + - kind::feature | ||
| 172 | + - priority::high | ||
| 173 | +milestone: v1.0 | ||
| 174 | +epic: MCP Integration | ||
| 175 | +body: | | ||
| 176 | + We need to add OAuth2 client credentials authentication for MCP servers | ||
| 177 | + that require it, such as context7.com. | ||
| 178 | + | ||
| 179 | + This involves implementing the OAuth2 flow in the HTTP client. | ||
| 180 | +tasks: | ||
| 181 | + - id: 1 | ||
| 182 | + title: Research OAuth2 requirements for context7.com | ||
| 183 | + state: open | ||
| 184 | + priority: high | ||
| 185 | + author: | ||
| 186 | + name: Developer Name | ||
| 187 | + email: developer@example.com | ||
| 188 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 189 | +comments: [] | ||
| 190 | +``` | ||
| 191 | + | ||
| 192 | +### Update an Existing Issue | ||
| 193 | + | ||
| 194 | +**Steps:** | ||
| 195 | +1. Read the existing issue file | ||
| 196 | +2. Update the specified fields | ||
| 197 | +3. Update `updatedAt` timestamp | ||
| 198 | +4. If state changes to closed, consider adding `closedAt` (not in IssueSpec, but can be added) | ||
| 199 | +5. If adding tasks/comments, ensure ids are unique within the issue | ||
| 200 | + | ||
| 201 | +**Note:** Only the author of a comment can edit or delete it through IssueSpec. | ||
| 202 | + | ||
| 203 | +### Add a Comment to an Issue | ||
| 204 | + | ||
| 205 | +**Steps:** | ||
| 206 | +1. Read the existing issue file | ||
| 207 | +2. Find the highest existing comment id | ||
| 208 | +3. Create new comment with next id | ||
| 209 | +4. Set author from current git config | ||
| 210 | +5. Set createdAt and updatedAt to current time | ||
| 211 | +6. Add comment to comments list | ||
| 212 | +7. Update issue's updatedAt | ||
| 213 | + | ||
| 214 | +**Example comment:** | ||
| 215 | +```yaml | ||
| 216 | +- id: 2 | ||
| 217 | + author: | ||
| 218 | + name: Reviewer Name | ||
| 219 | + email: reviewer@example.com | ||
| 220 | + createdAt: 2026-09-24T10:00:00.000Z | ||
| 221 | + body: | | ||
| 222 | + This looks good. One question: how do we handle token expiration? | ||
| 223 | +``` | ||
| 224 | + | ||
| 225 | +### Add a Task to an Issue | ||
| 226 | + | ||
| 227 | +**Steps:** | ||
| 228 | +1. Read the existing issue file | ||
| 229 | +2. Find the highest existing task id | ||
| 230 | +3. Create new task with next id | ||
| 231 | +4. Set author (can be anyone, unlike comments) | ||
| 232 | +5. Set createdAt to current time | ||
| 233 | +6. Add task to tasks list | ||
| 234 | +7. Update issue's updatedAt | ||
| 235 | + | ||
| 236 | +**Example task:** | ||
| 237 | +```yaml | ||
| 238 | +- id: 3 | ||
| 239 | + title: Implement token caching | ||
| 240 | + state: open | ||
| 241 | + priority: medium | ||
| 242 | + author: | ||
| 243 | + name: Developer Name | ||
| 244 | + email: developer@example.com | ||
| 245 | + createdAt: 2026-09-24T11:00:00.000Z | ||
| 246 | +``` | ||
| 247 | + | ||
| 248 | +### Create/Update a Label | ||
| 249 | + | ||
| 250 | +**Steps:** | ||
| 251 | +1. Read existing labels.yaml | ||
| 252 | +2. If creating: add new label to labels list | ||
| 253 | +3. If updating: find label by name and update fields | ||
| 254 | +4. Ensure name is unique | ||
| 255 | +5. Ensure color is valid hex format (#rrggbb) | ||
| 256 | + | ||
| 257 | +**Example:** | ||
| 258 | +```yaml | ||
| 259 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 260 | +labels: | ||
| 261 | + - name: mcp | ||
| 262 | + color: "#7b68ee" | ||
| 263 | + description: MCP-related features and integration | ||
| 264 | +``` | ||
| 265 | + | ||
| 266 | +### Create/Update a Milestone | ||
| 267 | + | ||
| 268 | +**Steps:** | ||
| 269 | +1. Read existing milestones.yaml | ||
| 270 | +2. If creating: add new milestone to milestones list | ||
| 271 | +3. If updating: find milestone by title and update fields | ||
| 272 | +4. Ensure title is unique | ||
| 273 | +5. Validate dueOn format (YYYY-MM-DD) | ||
| 274 | + | ||
| 275 | +### Create/Update an Epic | ||
| 276 | + | ||
| 277 | +**Steps:** | ||
| 278 | +1. Read existing epics.yaml | ||
| 279 | +2. If creating: add new epic to epics list | ||
| 280 | +3. If updating: find epic by title and update fields | ||
| 281 | +4. Ensure title is unique | ||
| 282 | +5. Ensure color is valid hex format (#rrggbb) | ||
| 283 | + | ||
| 284 | +### Link Related Issues | ||
| 285 | + | ||
| 286 | +**Steps:** | ||
| 287 | +1. Read both issue files | ||
| 288 | +2. Add each issue's id to the other's related list (one-side storage) | ||
| 289 | +3. Update both files' updatedAt | ||
| 290 | + | ||
| 291 | +**Note:** The relation is symmetric but stored on ONE side only. IssueSpec handles the symmetry when displaying. | ||
| 292 | + | ||
| 293 | +## File Naming Rules | ||
| 294 | + | ||
| 295 | +| Rule | Detail | | ||
| 296 | +| --- | --- | | ||
| 297 | +| Pattern | `<id>-<slug>.yaml`, where `<id>` is padded to at least four digits | | ||
| 298 | +| Slug | Title, accents folded to ASCII, lowercased, every run of non-alphanumeric characters replaced by `-`, trimmed to 48 characters | | ||
| 299 | +| Slug fallback | `issue`, when the title yields no usable characters | | ||
| 300 | +| Authority | The **id** identifies the issue. A file whose slug no longer matches its title is still found and read. | | ||
| 301 | +| Renaming | Changing a title rewrites the file under the new slug and deletes the old file | | ||
| 302 | + | ||
| 303 | +## Timestamp Generation | ||
| 304 | + | ||
| 305 | +Always use UTC time in ISO 8601 format with milliseconds: | ||
| 306 | +- Format: `YYYY-MM-DDTHH:MM:SS.sssZ` | ||
| 307 | +- Example: `2026-09-23T22:00:00.000Z` | ||
| 308 | +- Always end with `Z` for UTC | ||
| 309 | + | ||
| 310 | +## Validation Rules | ||
| 311 | + | ||
| 312 | +### Required Fields | ||
| 313 | +- Issue: id, title, state, author, createdAt, updatedAt | ||
| 314 | +- Task: id, title, state, author, createdAt | ||
| 315 | +- Comment: id, author, createdAt | ||
| 316 | +- Label: name, color | ||
| 317 | +- Milestone: title, state | ||
| 318 | +- Epic: title, state | ||
| 319 | + | ||
| 320 | +### Field Constraints | ||
| 321 | +- id: integer >= 1 | ||
| 322 | +- title: non-empty string | ||
| 323 | +- state: `open` or `closed` | ||
| 324 | +- name (label): non-empty string, unique | ||
| 325 | +- color: hex format `#rrggbb`, lowercased | ||
| 326 | +- priority: `none`, `low`, `medium`, `high`, `urgent` | ||
| 327 | +- dueOn: `YYYY-MM-DD` format | ||
| 328 | + | ||
| 329 | +### Cross-Reference Validation | ||
| 330 | +- Labels in an issue must exist in labels.yaml | ||
| 331 | +- Milestone in an issue must exist in milestones.yaml | ||
| 332 | +- Epic in an issue must exist in epics.yaml | ||
| 333 | +- Related issue ids must correspond to existing issues | ||
| 334 | + | ||
| 335 | +## Strict Formatting Rules | ||
| 336 | + | ||
| 337 | +**You must NEVER:** | ||
| 338 | +- Use tabs for indentation (always 2 spaces) | ||
| 339 | +- Hard-wrap prose in body or comments (one logical line per paragraph) | ||
| 340 | +- Include the `body` field when it would be empty (omit it entirely) | ||
| 341 | +- Use empty strings for absent fields (use YAML absent/omitted) | ||
| 342 | +- Change the file header line | ||
| 343 | + | ||
| 344 | +**You must ALWAYS:** | ||
| 345 | +- Use 2-space indentation throughout | ||
| 346 | +- Use literal block scalar (`|`) for multi-line body and comment body | ||
| 347 | +- Indent literal block content by 2 additional spaces | ||
| 348 | +- Keep the header line as the first line | ||
| 349 | +- Use lowercased hex colors | ||
| 350 | +- Ensure all timestamps are in UTC with milliseconds | ||
| 351 | + | ||
| 352 | +## Common Pitfalls | ||
| 353 | + | ||
| 354 | +1. **Body formatting**: Multi-line body must use `|` and proper indentation | ||
| 355 | +2. **Author consistency**: Use git config values, not hardcoded values | ||
| 356 | +3. **Timestamp consistency**: All timestamps must be current when creating/updating | ||
| 357 | +4. **Reference validation**: Always check that referenced labels/milestones/epics exist | ||
| 358 | +5. **Id uniqueness**: Never reuse ids, even for deleted items | ||
| 359 | +6. **File naming**: Slug must be properly generated from title | ||
| 360 | + | ||
| 361 | +## Examples | ||
| 362 | + | ||
| 363 | +### Minimal Valid Issue | ||
| 364 | + | ||
| 365 | +```yaml | ||
| 366 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 367 | +id: 1 | ||
| 368 | +title: Fix bug | ||
| 369 | +author: | ||
| 370 | + name: k33g | ||
| 371 | + email: ph.charriere@gmail.com | ||
| 372 | +state: open | ||
| 373 | +createdAt: 2026-09-23T22:00:00.000Z | ||
| 374 | +updatedAt: 2026-09-23T22:00:00.000Z | ||
| 375 | +labels: [] | ||
| 376 | +comments: [] | ||
| 377 | +``` | ||
| 378 | + | ||
| 379 | +### Issue with All Fields | ||
| 380 | + | ||
| 381 | +```yaml | ||
| 382 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 383 | +id: 2 | ||
| 384 | +title: Implement MCP Client Support | ||
| 385 | +state: open | ||
| 386 | +author: | ||
| 387 | + name: k33g | ||
| 388 | + email: ph.charriere@gmail.com | ||
| 389 | +createdAt: 2026-09-23T22:00:00.000Z | ||
| 390 | +updatedAt: 2026-09-24T10:00:00.000Z | ||
| 391 | +labels: | ||
| 392 | + - kind::feature | ||
| 393 | + - priority::high | ||
| 394 | +milestone: v1.0 | ||
| 395 | +epic: MCP Integration | ||
| 396 | +related: | ||
| 397 | + - 1 | ||
| 398 | +body: | | ||
| 399 | + This issue tracks the implementation of MCP client support in mini-me. | ||
| 400 | + | ||
| 401 | + ## Requirements | ||
| 402 | + | ||
| 403 | + - Support stdio transport | ||
| 404 | + - Support HTTP streamable transport | ||
| 405 | + - Handle authentication (bearer, API key, OAuth2) | ||
| 406 | + | ||
| 407 | + ## Design | ||
| 408 | + | ||
| 409 | + See the proposal in issue #4. | ||
| 410 | +tasks: | ||
| 411 | + - id: 1 | ||
| 412 | + title: Design configuration schema | ||
| 413 | + state: closed | ||
| 414 | + priority: high | ||
| 415 | + author: | ||
| 416 | + name: k33g | ||
| 417 | + email: ph.charriere@gmail.com | ||
| 418 | + createdAt: 2026-09-23T22:00:00.000Z | ||
| 419 | + closedAt: 2026-09-23T23:00:00.000Z | ||
| 420 | + - id: 2 | ||
| 421 | + title: Implement HTTP client | ||
| 422 | + state: open | ||
| 423 | + priority: high | ||
| 424 | + author: | ||
| 425 | + name: k33g | ||
| 426 | + email: ph.charriere@gmail.com | ||
| 427 | + createdAt: 2026-09-24T09:00:00.000Z | ||
| 428 | +comments: | ||
| 429 | + - id: 1 | ||
| 430 | + author: | ||
| 431 | + name: reviewer | ||
| 432 | + email: reviewer@example.com | ||
| 433 | + createdAt: 2026-09-24T08:00:00.000Z | ||
| 434 | + body: | | ||
| 435 | + This looks comprehensive. Have you considered the prompt cost impact? | ||
| 436 | +``` | ||
| 437 | + | ||
| 438 | +## Helper Functions | ||
| 439 | + | ||
| 440 | +When implementing this skill, use these helper patterns: | ||
| 441 | + | ||
| 442 | +### Generate Slug from Title | ||
| 443 | + | ||
| 444 | +```go | ||
| 445 | +func generateSlug(title string) string { | ||
| 446 | + // Lowercase | ||
| 447 | + // Replace accents with ASCII equivalents | ||
| 448 | + // Replace non-alphanumeric runs with single '-' | ||
| 449 | + // Trim to 48 characters | ||
| 450 | + // If empty, return "issue" | ||
| 451 | +} | ||
| 452 | +``` | ||
| 453 | + | ||
| 454 | +### Get Next Id | ||
| 455 | + | ||
| 456 | +```go | ||
| 457 | +func getNextId(files []string) int { | ||
| 458 | + // Extract ids from filenames (NNNN-*.yaml) | ||
| 459 | + // Find maximum id | ||
| 460 | + // Return max + 1 | ||
| 461 | +} | ||
| 462 | +``` | ||
| 463 | + | ||
| 464 | +### Get Git Author | ||
| 465 | + | ||
| 466 | +```bash | ||
| 467 | +git config user.name | ||
| 468 | +git config user.email | ||
| 469 | +``` | ||
| 470 | + | ||
| 471 | +### Get Current Timestamp | ||
| 472 | + | ||
| 473 | +```go | ||
| 474 | +func getTimestamp() string { | ||
| 475 | + return time.Now().UTC().Format("2006-01-02T15:04:05.000Z") | ||
| 476 | +} | ||
| 477 | +``` | ||
| 478 | + | ||
| 479 | +### Validate YAML | ||
| 480 | + | ||
| 481 | +Ensure the generated YAML: | ||
| 482 | +1. Can be parsed by a YAML parser | ||
| 483 | +2. Has all required fields | ||
| 484 | +3. Has valid field values | ||
| 485 | +4. Has proper indentation (2 spaces) | ||
| 486 | + | ||
| 487 | +## Final Notes | ||
| 488 | + | ||
| 489 | +This skill is designed to work alongside the IssueSpec VS Code extension. Files created or modified by this skill should be indistinguishable from those created through the extension's UI. Always prioritize correctness and validation over convenience. | ||
| 490 | + | ||
| 491 | +For more information, see: | ||
| 492 | +- [IssueSpec Documentation](https://codeberg.org/k33g/issuespec) | ||
| 493 | +- [Ticket File Format](https://codeberg.org/k33g/issuespec/src/branch/main/docs/en/reference/ticket-files.md) | ||
added
agent.mcp-example.yaml +55 -0 | new file mode 100644 | ||
| @@ -0,0 +1,55 @@ | ||
| 1 | +# Example configuration with MCP enabled | |
| 2 | +# This shows how to configure MCP servers with HTTP transport | |
| 3 | + | |
| 4 | +provider: dmr | |
| 5 | +model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M | |
| 6 | + | |
| 7 | +# Enable MCP client | |
| 8 | +mcp: | |
| 9 | + enabled: true | |
| 10 | + permissions: ask # Default for MCP tools: ask before executing | |
| 11 | + discoveryTimeout: 5s # Timeout for discovering tools at startup | |
| 12 | + | |
| 13 | + servers: | |
| 14 | + # Example: context7.com MCP server | |
| 15 | + - name: context7 | |
| 16 | + transport: http | |
| 17 | + url: https://mcp.context7.com/mcp | |
| 18 | + timeout: 30s | |
| 19 | + auth: | |
| 20 | + type: bearer | |
| 21 | + tokenEnv: CONTEXT7_TOKEN # Set this environment variable with your token | |
| 22 | + # Optional: filter which tools to expose | |
| 23 | + # tools: | |
| 24 | + # - search | |
| 25 | + # - get_documentation | |
| 26 | + | |
| 27 | + # Example: Another HTTP MCP server with API key auth | |
| 28 | + - name: my-api | |
| 29 | + transport: http | |
| 30 | + url: https://api.example.com/mcp | |
| 31 | + timeout: 15s | |
| 32 | + auth: | |
| 33 | + type: api_key | |
| 34 | + apiKeyEnv: MY_API_KEY | |
| 35 | + apiKeyHeader: X-API-Key # Optional: custom header name | |
| 36 | + headers: | |
| 37 | + X-Custom-Header: custom-value | |
| 38 | + | |
| 39 | + # Example: Stdio MCP server (NOT YET IMPLEMENTED) | |
| 40 | + # This will fail with "stdio transport for MCP is not yet implemented" | |
| 41 | + # - name: local-mcp | |
| 42 | + # transport: stdio | |
| 43 | + # command: mcp-server | |
| 44 | + # args: ["--stdio"] | |
| 45 | + # env: | |
| 46 | + # API_KEY: "{{.env.LOCAL_API_KEY}}" # Will be replaced with os.Getenv("LOCAL_API_KEY") | |
| 47 | + | |
| 48 | +# Rest of the configuration (optional overrides) | |
| 49 | +maxOutput: 16000 | |
| 50 | +maxTurns: 10 | |
| 51 | +previewLines: 20 | |
| 52 | + | |
| 53 | +# Note: When MCP is enabled, all discovered tools from the servers will be | |
| 54 | +# available to the model, prefixed with the server name. | |
| 55 | +# Example: context7_search, context7_get_documentation, etc. | |
| new file mode 100644 | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | +# Example configuration with MCP enabled | ||
| 2 | +# This shows how to configure MCP servers with HTTP transport | ||
| 3 | + | ||
| 4 | +provider: dmr | ||
| 5 | +model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M | ||
| 6 | + | ||
| 7 | +# Enable MCP client | ||
| 8 | +mcp: | ||
| 9 | + enabled: true | ||
| 10 | + permissions: ask # Default for MCP tools: ask before executing | ||
| 11 | + discoveryTimeout: 5s # Timeout for discovering tools at startup | ||
| 12 | + | ||
| 13 | + servers: | ||
| 14 | + # Example: context7.com MCP server | ||
| 15 | + - name: context7 | ||
| 16 | + transport: http | ||
| 17 | + url: https://mcp.context7.com/mcp | ||
| 18 | + timeout: 30s | ||
| 19 | + auth: | ||
| 20 | + type: bearer | ||
| 21 | + tokenEnv: CONTEXT7_TOKEN # Set this environment variable with your token | ||
| 22 | + # Optional: filter which tools to expose | ||
| 23 | + # tools: | ||
| 24 | + # - search | ||
| 25 | + # - get_documentation | ||
| 26 | + | ||
| 27 | + # Example: Another HTTP MCP server with API key auth | ||
| 28 | + - name: my-api | ||
| 29 | + transport: http | ||
| 30 | + url: https://api.example.com/mcp | ||
| 31 | + timeout: 15s | ||
| 32 | + auth: | ||
| 33 | + type: api_key | ||
| 34 | + apiKeyEnv: MY_API_KEY | ||
| 35 | + apiKeyHeader: X-API-Key # Optional: custom header name | ||
| 36 | + headers: | ||
| 37 | + X-Custom-Header: custom-value | ||
| 38 | + | ||
| 39 | + # Example: Stdio MCP server (NOT YET IMPLEMENTED) | ||
| 40 | + # This will fail with "stdio transport for MCP is not yet implemented" | ||
| 41 | + # - name: local-mcp | ||
| 42 | + # transport: stdio | ||
| 43 | + # command: mcp-server | ||
| 44 | + # args: ["--stdio"] | ||
| 45 | + # env: | ||
| 46 | + # API_KEY: "{{.env.LOCAL_API_KEY}}" # Will be replaced with os.Getenv("LOCAL_API_KEY") | ||
| 47 | + | ||
| 48 | +# Rest of the configuration (optional overrides) | ||
| 49 | +maxOutput: 16000 | ||
| 50 | +maxTurns: 10 | ||
| 51 | +previewLines: 20 | ||
| 52 | + | ||
| 53 | +# Note: When MCP is enabled, all discovered tools from the servers will be | ||
| 54 | +# available to the model, prefixed with the server name. | ||
| 55 | +# Example: context7_search, context7_get_documentation, etc. | ||
added
demo/MCP_SERVERS_GUIDE.md +630 -0 | new file mode 100644 | ||
| @@ -0,0 +1,630 @@ | ||
| 1 | +# Guide: Utiliser les Serveurs MCP avec mini-me | |
| 2 | + | |
| 3 | +Ce document explique comment configurer et utiliser des serveurs MCP avec mini-me : | |
| 4 | +- **Context7 MCP** - Documentation et contexte de code (nécessite une clé API) | |
| 5 | +- **GitHub MCP** - Accès à GitHub (nécessite un Personal Access Token) | |
| 6 | +- **Wikipedia MCP** - Accès complet à Wikipedia (gratuit, aucune authentification requise) | |
| 7 | +- **DuckDuckGo MCP (HasData)** - Recherche web via HasData (1,000 crédits gratuits/mois) - *actuellement désactivé dans la config par défaut* | |
| 8 | + | |
| 9 | +--- | |
| 10 | + | |
| 11 | +## 🚀 Configuration Rapide | |
| 12 | + | |
| 13 | +### 1. Utiliser le fichier de configuration fourni | |
| 14 | + | |
| 15 | +Un fichier de configuration prêt à l'emploi est fourni : `demo/agent.dmr.mcp.yaml` | |
| 16 | + | |
| 17 | +Il contient déjà la configuration pour les deux serveurs MCP. | |
| 18 | + | |
| 19 | +### 2. Exporter vos clés API | |
| 20 | + | |
| 21 | +#### Pour Context7 | |
| 22 | +```bash | |
| 23 | +export CONTEXT7_API_KEY="votre-clé-api-context7" | |
| 24 | +``` | |
| 25 | + | |
| 26 | +> ✅ **Où obtenir une clé Context7 ?** | |
| 27 | +> Inscrivez-vous sur [https://context7.com](https://context7.com) pour obtenir une clé API gratuite. | |
| 28 | + | |
| 29 | +#### Pour GitHub | |
| 30 | +```bash | |
| 31 | +export GITHUB_PERSONAL_ACCESS_TOKEN="votre-token-github" | |
| 32 | +``` | |
| 33 | + | |
| 34 | +> ✅ **Où obtenir un PAT GitHub ?** | |
| 35 | +> 1. Allez sur [https://github.com/settings/tokens](https://github.com/settings/tokens) | |
| 36 | +> 2. Cliquez sur "Generate new token" (classic) | |
| 37 | +> 3. Donnez un nom à votre token | |
| 38 | +> 4. Sélectionnez les scopes nécessaires (ex: `repo` pour l'accès aux dépôts) | |
| 39 | +> 5. Générez le token et copiez-le | |
| 40 | + | |
| 41 | +> ⚠️ **Scopes recommandés :** | |
| 42 | +> - `repo` : Accès complet aux dépôts (lecture/écriture) | |
| 43 | +> - `read:org` : Lire les informations d'organisation | |
| 44 | +> - `user` : Accès au profil utilisateur | |
| 45 | +> | |
| 46 | +> Pour une utilisation en lecture seule, `repo` (lecture) suffit. | |
| 47 | + | |
| 48 | +#### Pour Wikipedia | |
| 49 | +Aucune clé n'est nécessaire ! ✅ | |
| 50 | + | |
| 51 | +#### Pour DuckDuckGo (HasData) | |
| 52 | +```bash | |
| 53 | +export HASDATA_API_KEY="votre-clé-api-hasdata" | |
| 54 | +``` | |
| 55 | + | |
| 56 | +> ✅ **Où obtenir une clé HasData ?** | |
| 57 | +> Inscrivez-vous sur [https://app.hasdata.com](https://app.hasdata.com) pour obtenir une clé API gratuite. | |
| 58 | +> **Gratuit : 1,000 crédits/mois (100 recherches)** | |
| 59 | + | |
| 60 | +> ⚠️ **Note importante :** Le serveur DuckDuckGo utilisé est hébergé par HasData, pas par DuckDuckGo directement. | |
| 61 | + | |
| 62 | +### 3. Lancer mini-me avec la configuration MCP | |
| 63 | + | |
| 64 | +```bash | |
| 65 | +# Depuis le répertoire mini-me | |
| 66 | +./mm -config demo/agent.dmr.mcp.yaml | |
| 67 | +``` | |
| 68 | + | |
| 69 | +--- | |
| 70 | + | |
| 71 | +## 📝 Configuration Détailée | |
| 72 | + | |
| 73 | +### Structure de la Configuration MCP | |
| 74 | + | |
| 75 | +```yaml | |
| 76 | +mcp: | |
| 77 | + enabled: true # Active le support MCP | |
| 78 | + permissions: ask # Demander avant d'exécuter (recommandé) | |
| 79 | + discoveryTimeout: 10s # Timeout pour la découverte des outils | |
| 80 | + | |
| 81 | + servers: | |
| 82 | + - name: context7 | |
| 83 | + transport: http | |
| 84 | + url: https://mcp.context7.com/mcp | |
| 85 | + auth: | |
| 86 | + type: bearer | |
| 87 | + tokenEnv: CONTEXT7_API_KEY | |
| 88 | + headers: | |
| 89 | + Accept: "application/json, text/event-stream" | |
| 90 | + | |
| 91 | + - name: github | |
| 92 | + transport: http | |
| 93 | + url: https://api.githubcopilot.com/mcp/ | |
| 94 | + auth: | |
| 95 | + type: bearer | |
| 96 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 97 | + headers: | |
| 98 | + Accept: "application/json, text/event-stream" | |
| 99 | + | |
| 100 | + - name: wikipedia | |
| 101 | + transport: http | |
| 102 | + url: https://wikipedia-mcp.vercel.app/mcp | |
| 103 | + auth: | |
| 104 | + type: none | |
| 105 | + headers: | |
| 106 | + Accept: "application/json, text/event-stream" | |
| 107 | +``` | |
| 108 | + | |
| 109 | +### Options de Transport | |
| 110 | + | |
| 111 | +| Transport | Description | Auth Supportée | | |
| 112 | +|-----------|-------------|----------------| | |
| 113 | +| `http` | MCP sur HTTP (streamable) | none, bearer, api_key | | |
| 114 | +| `stdio` | MCP sur stdin/stdout | Non encore implémenté | | |
| 115 | + | |
| 116 | +> ⚠️ **Note sur les headers :** Certains serveurs MCP (comme Context7) nécessitent des headers HTTP spécifiques comme `Accept: application/json, text/event-stream`. Vous pouvez les ajouter via la section `headers` dans la configuration du serveur. | |
| 117 | + | |
| 118 | +### Types d'Authentification | |
| 119 | + | |
| 120 | +| Type | Description | Variables requises | | |
| 121 | +|------|-------------|-------------------| | |
| 122 | +| `none` | Aucune authentification | - | | |
| 123 | +| `bearer` | Token Bearer (Authorization: Bearer ...) | `tokenEnv` | | |
| 124 | +| `api_key` | Clé API (header personnalisable) | `apiKeyEnv`, `apiKeyHeader` (optionnel, défaut: Authorization) | | |
| 125 | + | |
| 126 | +--- | |
| 127 | + | |
| 128 | +## 🦆 DuckDuckGo MCP Server (HasData) | |
| 129 | + | |
| 130 | +### 📌 Qu'est-ce que c'est ? | |
| 131 | + | |
| 132 | +**DuckDuckGo MCP (HasData)** est un serveur MCP hébergé qui fournit des capacités de **recherche web** via l'API HasData. | |
| 133 | + | |
| 134 | +- **Gratuit** - 1,000 crédits/mois (100 recherches) sans carte bancaire | |
| 135 | +- **Simple** - Une tool principale: `hasdata_duckduckgo_serp_getSearchResults` | |
| 136 | +- **Régions** - Support de 37 régions différentes | |
| 137 | +- **Résultats structurés** - JSON avec position, titre, URL, snippet, etc. | |
| 138 | + | |
| 139 | +### 🔧 Configuration | |
| 140 | + | |
| 141 | +```yaml | |
| 142 | +- name: duckduckgo | |
| 143 | + transport: http | |
| 144 | + url: https://mcp.hasdata.com/mcp?apis=duckduckgo | |
| 145 | + auth: | |
| 146 | + type: api_key | |
| 147 | + apiKeyEnv: HASDATA_API_KEY | |
| 148 | + apiKeyHeader: x-api-key | |
| 149 | +``` | |
| 150 | + | |
| 151 | +> ⚠️ **Note importante :** Le serveur DuckDuckGo MCP officiel est hébergé par HasData. | |
| 152 | +> L'URL `https://duckduckgo.com/mcp` n'est PAS un serveur MCP - c'est juste le site web de DuckDuckGo. | |
| 153 | +> | |
| 154 | +> **Test de connectivité :** | |
| 155 | +> ```bash | |
| 156 | +> curl -I -H "x-api-key: VOTRE_CLE" https://mcp.hasdata.com/mcp?apis=duckduckgo | |
| 157 | +> ``` | |
| 158 | +> Doit retourner HTTP 200 ou 401 (si la clé est invalide). | |
| 159 | + | |
| 160 | +### 💡 Utilisation | |
| 161 | + | |
| 162 | +Une fois configuré, mini-me aura accès à l'outil : **`hasdata_duckduckgo_serp_getSearchResults`** | |
| 163 | + | |
| 164 | +**Exemples de requêtes :** | |
| 165 | + | |
| 166 | +``` | |
| 167 | +"Recherche des dernières nouvelles sur l'IA" | |
| 168 | +-> Le modèle peut utiliser hasdata_duckduckgo_serp_getSearchResults avec votre requête | |
| 169 | + | |
| 170 | +"Quelle est la documentation officielle de Go 1.22 ?" | |
| 171 | +-> Le modèle peut rechercher et retourner des liens pertinents | |
| 172 | +``` | |
| 173 | + | |
| 174 | +**Paramètres disponibles :** | |
| 175 | +- `q` (requis) : La requête de recherche | |
| 176 | +- `kl` : Région au format `<pays>-<langue>` (ex: `fr-fr`, `us-en`, `de-de`) | |
| 177 | +- `cc` : Code pays à 2 lettres | |
| 178 | +- `setLang` : Langue de l'interface | |
| 179 | +- `safeSearch` : `off`, `moderate`, ou `strict` | |
| 180 | +- `max_results` : Nombre maximum de résultats (par défaut: 10) | |
| 181 | + | |
| 182 | +> ⚠️ **Coût :** Chaque appel coûte **10 crédits**. Le tier gratuit offre 1,000 crédits/mois (100 recherches). | |
| 183 | + | |
| 184 | +### 🎯 Cas d'usage | |
| 185 | + | |
| 186 | +- Recherche de documentation | |
| 187 | +- Vérification de faits récents | |
| 188 | +- Trouver des exemples de code | |
| 189 | +- Résoudre des problèmes avec des solutions en ligne | |
| 190 | + | |
| 191 | +### ⚠️ Limitations | |
| 192 | + | |
| 193 | +- Une seule tool disponible via HasData: `hasdata_duckduckgo_serp_getSearchResults` | |
| 194 | + | |
| 195 | +--- | |
| 196 | + | |
| 197 | +## 🐙 GitHub MCP Server | |
| 198 | + | |
| 199 | +### 📌 Qu'est-ce que c'est ? | |
| 200 | + | |
| 201 | +**GitHub MCP** est le serveur MCP **officiel** de GitHub, qui fournit un accès complet à l'API GitHub. | |
| 202 | + | |
| 203 | +- **Authentification** : GitHub Personal Access Token (PAT) requis | |
| 204 | +- **Transport** : Streamable HTTP | |
| 205 | +- **Outils** : Accès aux dépôts, code, issues, pull requests, etc. | |
| 206 | +- **Official** : Maintenu par GitHub | |
| 207 | + | |
| 208 | +### 🔧 Configuration | |
| 209 | + | |
| 210 | +```yaml | |
| 211 | +- name: github | |
| 212 | + transport: http | |
| 213 | + url: https://api.githubcopilot.com/mcp/ | |
| 214 | + auth: | |
| 215 | + type: bearer | |
| 216 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 217 | + headers: | |
| 218 | + Accept: "application/json, text/event-stream" | |
| 219 | +``` | |
| 220 | + | |
| 221 | +### 💡 Utilisation | |
| 222 | + | |
| 223 | +Une fois configuré, mini-me aura accès aux outils GitHub (préfixés par `github_`). | |
| 224 | + | |
| 225 | +**Exemples d'outils typiques :** | |
| 226 | +- `list_repositories` - Lister vos dépôts | |
| 227 | +- `read_repository` - Lire le contenu d'un dépôt | |
| 228 | +- `search_code` - Rechercher dans le code | |
| 229 | +- `get_issue` - Obtenir une issue | |
| 230 | +- `list_pull_requests` - Lister les pull requests | |
| 231 | + | |
| 232 | +> ⚠️ **Note :** Les outils disponibles dépendent des **scopes** de votre PAT. | |
| 233 | + | |
| 234 | +**Exemples de requêtes :** | |
| 235 | + | |
| 236 | +``` | |
| 237 | +"Quels sont mes dépôts publics sur GitHub ?" | |
| 238 | +-> Le modèle peut utiliser github_list_repositories | |
| 239 | + | |
| 240 | +"Montre-moi le fichier README.md de mon dépôt mini-me" | |
| 241 | +-> Le modèle peut utiliser github_read_repository | |
| 242 | + | |
| 243 | +"Recherche l'utilisation de modelcontextprotocol dans mon code" | |
| 244 | +-> Le modèle peut utiliser github_search_code | |
| 245 | +``` | |
| 246 | + | |
| 247 | +### 🎯 Cas d'usage | |
| 248 | + | |
| 249 | +- Explorer vos dépôts GitHub | |
| 250 | +- Lire des fichiers spécifiques | |
| 251 | +- Rechercher dans votre code | |
| 252 | +- Gérer les issues et pull requests | |
| 253 | +- Analyser la structure de vos projets | |
| 254 | + | |
| 255 | +### 🔑 Authentification et Scopes | |
| 256 | + | |
| 257 | +Votre PAT doit avoir les **scopes** appropriés pour les outils que vous voulez utiliser : | |
| 258 | + | |
| 259 | +| Outils | Scopes requis | | |
| 260 | +|-------|---------------| | |
| 261 | +| Lister les dépôts | `repo` (lecture) | | |
| 262 | +| Lire les fichiers | `repo` (lecture) | | |
| 263 | +| Rechercher dans le code | `repo` (lecture) | | |
| 264 | +| Gérer les issues | `repo`, `issues` | | |
| 265 | +| Gérer les PR | `repo`, `pull_requests` | | |
| 266 | + | |
| 267 | +> ✅ **Pour commencer** : Utilisez un PAT avec le scope `repo` (lecture seulement) | |
| 268 | + | |
| 269 | +--- | |
| 270 | + | |
| 271 | +## 📚 Wikipedia MCP Server | |
| 272 | + | |
| 273 | +### 📌 Qu'est-ce que c'est ? | |
| 274 | + | |
| 275 | +**Wikipedia MCP** est un serveur MCP hébergé qui fournit un **accès complet à Wikipedia**. | |
| 276 | + | |
| 277 | +- **Gratuit** - Aucune clé API nécessaire | |
| 278 | +- **Complet** - Recherche, articles, résumés, sections, liens, etc. | |
| 279 | +- **Multi-langues** - Support de nombreuses langues | |
| 280 | +- **Aucune authentification** requise | |
| 281 | + | |
| 282 | +### 🔧 Configuration | |
| 283 | + | |
| 284 | +```yaml | |
| 285 | +- name: wikipedia | |
| 286 | + transport: http | |
| 287 | + url: https://wikipedia-mcp.vercel.app/mcp | |
| 288 | + auth: | |
| 289 | + type: none | |
| 290 | + headers: | |
| 291 | + Accept: "application/json, text/event-stream" | |
| 292 | +``` | |
| 293 | + | |
| 294 | +### 💡 Utilisation | |
| 295 | + | |
| 296 | +Une fois configuré, mini-me aura accès aux outils Wikipedia suivants (préfixés par `wikipedia_`) : | |
| 297 | +- `wikipedia_search_wikipedia` - Rechercher des articles | |
| 298 | +- `wikipedia_get_article` - Récupérer un article complet | |
| 299 | +- `wikipedia_get_summary` - Obtenir un résumé | |
| 300 | +- `wikipedia_get_sections` - Lister les sections d'un article | |
| 301 | +- `wikipedia_get_links` - Obtenir les liens d'un article | |
| 302 | +- `wikipedia_get_coordinates` - Coordonnées géographiques | |
| 303 | +- `wikipedia_get_related_topics` - Sujets liés | |
| 304 | +- `wikipedia_summarize_article_for_query` - Résumer un article pour une requête | |
| 305 | +- `wikipedia_summarize_article_section` - Résumer une section | |
| 306 | +- `wikipedia_extract_key_facts` - Extraire les faits clés | |
| 307 | +- `wikipedia_test_wikipedia_connectivity` - Tester la connectivité | |
| 308 | +- `wikipedia_list_supported_countries` - Lister les pays supportés | |
| 309 | +- `wikipedia_multi_search_wikipedia` - Recherche multiple | |
| 310 | + | |
| 311 | +**Exemples de requêtes :** | |
| 312 | + | |
| 313 | +``` | |
| 314 | +"Qu'est-ce que le Model Context Protocol ?" | |
| 315 | +-> Le modèle peut utiliser wikipedia_search_wikipedia pour trouver l'article | |
| 316 | + | |
| 317 | +"Donne-moi un résumé de l'article sur la relativité" | |
| 318 | +-> Le modèle peut utiliser wikipedia_get_summary | |
| 319 | + | |
| 320 | +"Quels sont les liens dans l'article sur l'IA ?" | |
| 321 | +-> Le modèle peut utiliser wikipedia_get_links | |
| 322 | +``` | |
| 323 | + | |
| 324 | +### 🎯 Cas d'usage | |
| 325 | + | |
| 326 | +- Recherche d'informations générales | |
| 327 | +- Vérification de faits | |
| 328 | +- Obtenir des définitions et explications | |
| 329 | +- Explorer des sujets de manière approfondie | |
| 330 | + | |
| 331 | +### ✅ Avantages | |
| 332 | + | |
| 333 | +- **Gratuit** - Pas de limitations de quota connues | |
| 334 | +- **Immédiat** - Pas besoin de s'inscrire | |
| 335 | +- **Complet** - Accès à toutes les fonctionnalités Wikipedia | |
| 336 | +- Les résultats sont limités par le format de réponse de DuckDuckGo | |
| 337 | +- Pas de contexte de conversation (chaque recherche est indépendante) | |
| 338 | + | |
| 339 | +--- | |
| 340 | + | |
| 341 | +## 🎯 Context7 MCP Server | |
| 342 | + | |
| 343 | +### 📌 Qu'est-ce que c'est ? | |
| 344 | + | |
| 345 | +**Context7** est un serveur MCP qui fournit : | |
| 346 | +- Recherche dans la documentation officielle | |
| 347 | +- Compréhension du contexte de code | |
| 348 | +- Génération de code contextuel | |
| 349 | + | |
| 350 | +### 🔑 Authentification Requise | |
| 351 | + | |
| 352 | +Context7 nécessite une **clé API**. | |
| 353 | + | |
| 354 | +1. **Obtenez une clé API :** | |
| 355 | + - Allez sur [https://context7.com](https://context7.com) | |
| 356 | + - Inscrivez-vous (gratuit) | |
| 357 | + - Récupérez votre clé API dans le tableau de bord | |
| 358 | + | |
| 359 | +2. **Exportez la clé :** | |
| 360 | + ```bash | |
| 361 | + export CONTEXT7_API_KEY="votre-clé-api" | |
| 362 | + ``` | |
| 363 | + | |
| 364 | +3. **Ajoutez au shell profile (optionnel) :** | |
| 365 | + ```bash | |
| 366 | + # Pour bash/zsh | |
| 367 | + echo 'export CONTEXT7_API_KEY="votre-clé-api"' >> ~/.bashrc | |
| 368 | + source ~/.bashrc | |
| 369 | + ``` | |
| 370 | + | |
| 371 | +### 🔧 Configuration | |
| 372 | + | |
| 373 | +```yaml | |
| 374 | +- name: context7 | |
| 375 | + transport: http | |
| 376 | + url: https://mcp.context7.com/mcp | |
| 377 | + timeout: 60s # Temps plus long pour les requêtes complexes | |
| 378 | + auth: | |
| 379 | + type: bearer | |
| 380 | + tokenEnv: CONTEXT7_API_KEY | |
| 381 | +``` | |
| 382 | + | |
| 383 | +### 💡 Utilisation | |
| 384 | + | |
| 385 | +Une fois configuré, mini-me aura accès aux outils Context7, préfixés par `context7_` : | |
| 386 | +- Les outils disponibles dépendent de ce que le serveur Context7 expose | |
| 387 | +- Exemples typiques : `search_documentation`, `get_code_context`, etc. | |
| 388 | + | |
| 389 | +**Exemples de requêtes :** | |
| 390 | + | |
| 391 | +``` | |
| 392 | +"Comment utiliser le package http en Go ?" | |
| 393 | +-> Le modèle peut utiliser context7_search_documentation pour trouver la doc officielle | |
| 394 | + | |
| 395 | +"Explique moi ce code React" | |
| 396 | +-> Le modèle peut utiliser context7_get_code_context pour comprendre le contexte | |
| 397 | +``` | |
| 398 | + | |
| 399 | +### 🎯 Cas d'usage | |
| 400 | + | |
| 401 | +- Recherche de documentation technique | |
| 402 | +- Compréhension de bibliothèques et frameworks | |
| 403 | +- Génération de code avec contexte | |
| 404 | +- Debugging avec des exemples réels | |
| 405 | + | |
| 406 | +### ⚠️ Limitations | |
| 407 | + | |
| 408 | +- Nécessite une connexion internet | |
| 409 | +- Dépend de la disponibilité du service Context7 | |
| 410 | +- Les outils disponibles peuvent changer | |
| 411 | + | |
| 412 | +--- | |
| 413 | + | |
| 414 | +## 🔄 Filtrage des Outils | |
| 415 | + | |
| 416 | +Par défaut, **tous les outils** d'un serveur MCP sont exposés à mini-me. | |
| 417 | + | |
| 418 | +Vous pouvez **filtrer** les outils en spécifiant une liste : | |
| 419 | + | |
| 420 | +```yaml | |
| 421 | +- name: context7 | |
| 422 | + transport: http | |
| 423 | + url: https://mcp.context7.com/mcp | |
| 424 | + auth: | |
| 425 | + type: bearer | |
| 426 | + tokenEnv: CONTEXT7_API_KEY | |
| 427 | + tools: # Seuls ces outils seront exposés | |
| 428 | + - search_documentation | |
| 429 | + - get_code_context | |
| 430 | +``` | |
| 431 | + | |
| 432 | +Cela réduit : | |
| 433 | +- Le nombre d'outils dans le prompt (économie de tokens) | |
| 434 | +- La surface d'attaque potentielle | |
| 435 | +- Le bruit dans la liste des outils | |
| 436 | + | |
| 437 | +--- | |
| 438 | + | |
| 439 | +## 🛡️ Sécurité | |
| 440 | + | |
| 441 | +### Permissions | |
| 442 | + | |
| 443 | +Deux modes de permissions sont disponibles : | |
| 444 | + | |
| 445 | +| Mode | Comportement | Recommandation | | |
| 446 | +|------|-------------|----------------| | |
| 447 | +| `ask` | Demande confirmation avant chaque appel MCP | ✅ Recommandé | | |
| 448 | +| `auto` | Exécute automatiquement (comme bash) | ⚠️ À utiliser avec prudence | | |
| 449 | + | |
| 450 | +**Configuration globale :** | |
| 451 | +```yaml | |
| 452 | +mcp: | |
| 453 | + permissions: ask # Mode par défaut pour tous les serveurs | |
| 454 | +``` | |
| 455 | + | |
| 456 | +**Override par serveur :** | |
| 457 | +```yaml | |
| 458 | +servers: | |
| 459 | + - name: duckduckgo | |
| 460 | + permissions: auto # Auto pour ce serveur spécifique | |
| 461 | + ... | |
| 462 | + - name: context7 | |
| 463 | + permissions: ask # Ask pour ce serveur | |
| 464 | + ... | |
| 465 | +``` | |
| 466 | + | |
| 467 | +### Bonnes Pratiques | |
| 468 | + | |
| 469 | +1. **Utilisez toujours `permissions: ask`** pour les serveurs que vous ne connaissez pas | |
| 470 | +2. **Ne partagez pas vos clés API** dans les fichiers de configuration | |
| 471 | +3. **Utilisez des variables d'environnement** pour les clés sensibles | |
| 472 | +4. **Filtrez les outils** que vous n'utilisez pas | |
| 473 | +5. **Testez en local** avant de déployer en production | |
| 474 | + | |
| 475 | +--- | |
| 476 | + | |
| 477 | +## 🛠️ Dépannage | |
| 478 | + | |
| 479 | +### Problème : "mcp: enabled but no servers configured" | |
| 480 | + | |
| 481 | +**Cause :** MCP est activé mais aucun serveur n'est configuré. | |
| 482 | + | |
| 483 | +**Solution :** Ajoutez au moins un serveur dans la liste `servers`. | |
| 484 | + | |
| 485 | +--- | |
| 486 | + | |
| 487 | +### Problème : "mcp server X: discovery failed: connection refused" | |
| 488 | + | |
| 489 | +**Causes possibles :** | |
| 490 | +- Le serveur MCP est hors ligne | |
| 491 | +- L'URL est incorrecte | |
| 492 | +- Problème de réseau | |
| 493 | +- Authentification manquante ou incorrecte | |
| 494 | +- Headers HTTP manquants (comme `Accept: application/json, text/event-stream`) | |
| 495 | + | |
| 496 | +**Solutions :** | |
| 497 | +- Vérifiez l'URL du serveur | |
| 498 | +- Vérifiez que les variables d'environnement pour l'authentification sont définies | |
| 499 | +- Pour Context7, assurez-vous que le header `Accept` est correctement configuré | |
| 500 | +- Testez la connectivité : `curl -v -H "Authorization: Bearer VOTRE_TOKEN" -H "Accept: application/json, text/event-stream" https://mcp.context7.com/mcp` | |
| 501 | +- Essayez plus tard | |
| 502 | + | |
| 503 | +--- | |
| 504 | + | |
| 505 | +### Problème : "missing bearer token for env var CONTEXT7_API_KEY" | |
| 506 | + | |
| 507 | +**Cause :** La variable d'environnement `CONTEXT7_API_KEY` n'est pas définie. | |
| 508 | + | |
| 509 | +**Solution :** | |
| 510 | +```bash | |
| 511 | +export CONTEXT7_API_KEY="votre-clé" | |
| 512 | +./mm -config demo/agent.dmr.mcp.yaml | |
| 513 | +``` | |
| 514 | + | |
| 515 | +--- | |
| 516 | + | |
| 517 | +### Problème : "missing api_key for env var HASDATA_API_KEY" | |
| 518 | + | |
| 519 | +**Cause :** La variable d'environnement `HASDATA_API_KEY` n'est pas définie. | |
| 520 | + | |
| 521 | +**Solution :** | |
| 522 | +```bash | |
| 523 | +export HASDATA_API_KEY="votre-clé" | |
| 524 | +./mm -config demo/agent.dmr.mcp.yaml | |
| 525 | +``` | |
| 526 | + | |
| 527 | +> ✅ **Comment obtenir une clé HasData :** | |
| 528 | +> 1. Inscrivez-vous gratuitement sur [https://app.hasdata.com](https://app.hasdata.com) | |
| 529 | +> 2. Récupérez votre clé API dans le tableau de bord | |
| 530 | +> 3. Vous obtenez **1,000 crédits gratuits par mois** (100 recherches) | |
| 531 | + | |
| 532 | +--- | |
| 533 | + | |
| 534 | +### Problème : Les outils MCP n'apparaissent pas dans la bannière | |
| 535 | + | |
| 536 | +**Causes possibles :** | |
| 537 | +- MCP est désactivé | |
| 538 | +- La discovery a échoué silencieusement | |
| 539 | +- Tous les outils ont été filtrés (binary I/O) | |
| 540 | + | |
| 541 | +**Solution :** | |
| 542 | +- Vérifiez les warnings dans la bannière de démarrage | |
| 543 | +- Vérifiez que `mcp.enabled: true` | |
| 544 | +- Essayez avec un timeout de discovery plus long | |
| 545 | + | |
| 546 | +--- | |
| 547 | + | |
| 548 | +## 📊 Exemples Complets | |
| 549 | + | |
| 550 | +### Configuration Minimale (DuckDuckGo seul) | |
| 551 | + | |
| 552 | +```yaml | |
| 553 | +mcp: | |
| 554 | + enabled: true | |
| 555 | + permissions: ask | |
| 556 | + servers: | |
| 557 | + - name: duckduckgo | |
| 558 | + transport: http | |
| 559 | + url: https://mcp.hasdata.com/mcp?apis=duckduckgo | |
| 560 | + auth: | |
| 561 | + type: api_key | |
| 562 | + apiKeyEnv: HASDATA_API_KEY | |
| 563 | + apiKeyHeader: x-api-key | |
| 564 | +``` | |
| 565 | + | |
| 566 | +### Configuration Complète (Les deux serveurs) | |
| 567 | + | |
| 568 | +Voir `demo/agent.dmr.mcp.yaml` pour un exemple complet. | |
| 569 | + | |
| 570 | +--- | |
| 571 | + | |
| 572 | +## 🎓 Astuces | |
| 573 | + | |
| 574 | +### 1. Nom des Outils | |
| 575 | + | |
| 576 | +Les outils MCP sont **préfixés avec le nom du serveur** : | |
| 577 | +- `duckduckgo_search` | |
| 578 | +- `context7_search_documentation` | |
| 579 | + | |
| 580 | +Cela évite les collisions et permet de savoir d'où vient chaque outil. | |
| 581 | + | |
| 582 | +### 2. Découverte au Démarrage | |
| 583 | + | |
| 584 | +La découverte des outils MCP se fait **au démarrage** de mini-me, pas à la première utilisation. | |
| 585 | +Cela signifie : | |
| 586 | +- Un temps de démarrage légèrement plus long | |
| 587 | +- La liste des outils est **statique** pour la session | |
| 588 | +- Si un serveur meurt pendant la session, les outils deviennent indisponibles | |
| 589 | + | |
| 590 | +### 3. Texte Seulement | |
| 591 | + | |
| 592 | +Mini-me **ignore les outils binaires**. Seuls les outils avec entrée/sortie texte sont exposés. | |
| 593 | + | |
| 594 | +Cela signifie que les outils qui manipulent des images, PDFs, ou autres données binaires ne seront **pas disponibles**. | |
| 595 | + | |
| 596 | +--- | |
| 597 | + | |
| 598 | +## 📚 Références | |
| 599 | + | |
| 600 | +- [Context7 Official Site](https://context7.com) | |
| 601 | +- [DuckDuckGo](https://duckduckgo.com) | |
| 602 | +- [HasData DuckDuckGo MCP Server](https://github.com/HasData/duckduckgo-mcp) | |
| 603 | +- [HasData Dashboard](https://app.hasdata.com) | |
| 604 | +- [MCP Specification](https://modelcontextprotocol.io/) | |
| 605 | + | |
| 606 | +--- | |
| 607 | + | |
| 608 | +## 🔄 Changelog | |
| 609 | + | |
| 610 | +| Date | Changement | | |
| 611 | +|------|------------| | |
| 612 | +| 2026-09-23 | Guide initial créé | | |
| 613 | +| 2026-09-23 | Configuration MCP ajoutée à agent.dmr.mcp.yaml | | |
| 614 | +| 2026-09-23 | Mise à jour : DuckDuckGo utilise maintenant le serveur HasData (https://mcp.hasdata.com) | | |
| 615 | +| 2026-09-23 | Ajout du header Accept pour Context7 | | |
| 616 | +| 2026-09-23 | Correction des URLs et authentifications | | |
| 617 | + | |
| 618 | +--- | |
| 619 | + | |
| 620 | +## 💬 Support | |
| 621 | + | |
| 622 | +Si vous rencontrez des problèmes avec les serveurs MCP : | |
| 623 | +1. Vérifiez les warnings dans la bannière de mini-me | |
| 624 | +2. Consultez ce guide | |
| 625 | +3. Vérifiez les logs et les messages d'erreur | |
| 626 | +4. Ouvrez un issue avec les détails du problème | |
| 627 | + | |
| 628 | +--- | |
| 629 | + | |
| 630 | +**Note :** Les serveurs MCP sont des services tiers. Leur disponibilité, leurs outils et leur comportement peuvent changer sans préavis. Mini-me fait de son mieux pour intégrer ces services de manière sûre et fiable, mais ne peut pas garantir leur fonctionnement. | |
| new file mode 100644 | |||
| @@ -0,0 +1,630 @@ | |||
| 1 | +# Guide: Utiliser les Serveurs MCP avec mini-me | ||
| 2 | + | ||
| 3 | +Ce document explique comment configurer et utiliser des serveurs MCP avec mini-me : | ||
| 4 | +- **Context7 MCP** - Documentation et contexte de code (nécessite une clé API) | ||
| 5 | +- **GitHub MCP** - Accès à GitHub (nécessite un Personal Access Token) | ||
| 6 | +- **Wikipedia MCP** - Accès complet à Wikipedia (gratuit, aucune authentification requise) | ||
| 7 | +- **DuckDuckGo MCP (HasData)** - Recherche web via HasData (1,000 crédits gratuits/mois) - *actuellement désactivé dans la config par défaut* | ||
| 8 | + | ||
| 9 | +--- | ||
| 10 | + | ||
| 11 | +## 🚀 Configuration Rapide | ||
| 12 | + | ||
| 13 | +### 1. Utiliser le fichier de configuration fourni | ||
| 14 | + | ||
| 15 | +Un fichier de configuration prêt à l'emploi est fourni : `demo/agent.dmr.mcp.yaml` | ||
| 16 | + | ||
| 17 | +Il contient déjà la configuration pour les deux serveurs MCP. | ||
| 18 | + | ||
| 19 | +### 2. Exporter vos clés API | ||
| 20 | + | ||
| 21 | +#### Pour Context7 | ||
| 22 | +```bash | ||
| 23 | +export CONTEXT7_API_KEY="votre-clé-api-context7" | ||
| 24 | +``` | ||
| 25 | + | ||
| 26 | +> ✅ **Où obtenir une clé Context7 ?** | ||
| 27 | +> Inscrivez-vous sur [https://context7.com](https://context7.com) pour obtenir une clé API gratuite. | ||
| 28 | + | ||
| 29 | +#### Pour GitHub | ||
| 30 | +```bash | ||
| 31 | +export GITHUB_PERSONAL_ACCESS_TOKEN="votre-token-github" | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +> ✅ **Où obtenir un PAT GitHub ?** | ||
| 35 | +> 1. Allez sur [https://github.com/settings/tokens](https://github.com/settings/tokens) | ||
| 36 | +> 2. Cliquez sur "Generate new token" (classic) | ||
| 37 | +> 3. Donnez un nom à votre token | ||
| 38 | +> 4. Sélectionnez les scopes nécessaires (ex: `repo` pour l'accès aux dépôts) | ||
| 39 | +> 5. Générez le token et copiez-le | ||
| 40 | + | ||
| 41 | +> ⚠️ **Scopes recommandés :** | ||
| 42 | +> - `repo` : Accès complet aux dépôts (lecture/écriture) | ||
| 43 | +> - `read:org` : Lire les informations d'organisation | ||
| 44 | +> - `user` : Accès au profil utilisateur | ||
| 45 | +> | ||
| 46 | +> Pour une utilisation en lecture seule, `repo` (lecture) suffit. | ||
| 47 | + | ||
| 48 | +#### Pour Wikipedia | ||
| 49 | +Aucune clé n'est nécessaire ! ✅ | ||
| 50 | + | ||
| 51 | +#### Pour DuckDuckGo (HasData) | ||
| 52 | +```bash | ||
| 53 | +export HASDATA_API_KEY="votre-clé-api-hasdata" | ||
| 54 | +``` | ||
| 55 | + | ||
| 56 | +> ✅ **Où obtenir une clé HasData ?** | ||
| 57 | +> Inscrivez-vous sur [https://app.hasdata.com](https://app.hasdata.com) pour obtenir une clé API gratuite. | ||
| 58 | +> **Gratuit : 1,000 crédits/mois (100 recherches)** | ||
| 59 | + | ||
| 60 | +> ⚠️ **Note importante :** Le serveur DuckDuckGo utilisé est hébergé par HasData, pas par DuckDuckGo directement. | ||
| 61 | + | ||
| 62 | +### 3. Lancer mini-me avec la configuration MCP | ||
| 63 | + | ||
| 64 | +```bash | ||
| 65 | +# Depuis le répertoire mini-me | ||
| 66 | +./mm -config demo/agent.dmr.mcp.yaml | ||
| 67 | +``` | ||
| 68 | + | ||
| 69 | +--- | ||
| 70 | + | ||
| 71 | +## 📝 Configuration Détailée | ||
| 72 | + | ||
| 73 | +### Structure de la Configuration MCP | ||
| 74 | + | ||
| 75 | +```yaml | ||
| 76 | +mcp: | ||
| 77 | + enabled: true # Active le support MCP | ||
| 78 | + permissions: ask # Demander avant d'exécuter (recommandé) | ||
| 79 | + discoveryTimeout: 10s # Timeout pour la découverte des outils | ||
| 80 | + | ||
| 81 | + servers: | ||
| 82 | + - name: context7 | ||
| 83 | + transport: http | ||
| 84 | + url: https://mcp.context7.com/mcp | ||
| 85 | + auth: | ||
| 86 | + type: bearer | ||
| 87 | + tokenEnv: CONTEXT7_API_KEY | ||
| 88 | + headers: | ||
| 89 | + Accept: "application/json, text/event-stream" | ||
| 90 | + | ||
| 91 | + - name: github | ||
| 92 | + transport: http | ||
| 93 | + url: https://api.githubcopilot.com/mcp/ | ||
| 94 | + auth: | ||
| 95 | + type: bearer | ||
| 96 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 97 | + headers: | ||
| 98 | + Accept: "application/json, text/event-stream" | ||
| 99 | + | ||
| 100 | + - name: wikipedia | ||
| 101 | + transport: http | ||
| 102 | + url: https://wikipedia-mcp.vercel.app/mcp | ||
| 103 | + auth: | ||
| 104 | + type: none | ||
| 105 | + headers: | ||
| 106 | + Accept: "application/json, text/event-stream" | ||
| 107 | +``` | ||
| 108 | + | ||
| 109 | +### Options de Transport | ||
| 110 | + | ||
| 111 | +| Transport | Description | Auth Supportée | | ||
| 112 | +|-----------|-------------|----------------| | ||
| 113 | +| `http` | MCP sur HTTP (streamable) | none, bearer, api_key | | ||
| 114 | +| `stdio` | MCP sur stdin/stdout | Non encore implémenté | | ||
| 115 | + | ||
| 116 | +> ⚠️ **Note sur les headers :** Certains serveurs MCP (comme Context7) nécessitent des headers HTTP spécifiques comme `Accept: application/json, text/event-stream`. Vous pouvez les ajouter via la section `headers` dans la configuration du serveur. | ||
| 117 | + | ||
| 118 | +### Types d'Authentification | ||
| 119 | + | ||
| 120 | +| Type | Description | Variables requises | | ||
| 121 | +|------|-------------|-------------------| | ||
| 122 | +| `none` | Aucune authentification | - | | ||
| 123 | +| `bearer` | Token Bearer (Authorization: Bearer ...) | `tokenEnv` | | ||
| 124 | +| `api_key` | Clé API (header personnalisable) | `apiKeyEnv`, `apiKeyHeader` (optionnel, défaut: Authorization) | | ||
| 125 | + | ||
| 126 | +--- | ||
| 127 | + | ||
| 128 | +## 🦆 DuckDuckGo MCP Server (HasData) | ||
| 129 | + | ||
| 130 | +### 📌 Qu'est-ce que c'est ? | ||
| 131 | + | ||
| 132 | +**DuckDuckGo MCP (HasData)** est un serveur MCP hébergé qui fournit des capacités de **recherche web** via l'API HasData. | ||
| 133 | + | ||
| 134 | +- **Gratuit** - 1,000 crédits/mois (100 recherches) sans carte bancaire | ||
| 135 | +- **Simple** - Une tool principale: `hasdata_duckduckgo_serp_getSearchResults` | ||
| 136 | +- **Régions** - Support de 37 régions différentes | ||
| 137 | +- **Résultats structurés** - JSON avec position, titre, URL, snippet, etc. | ||
| 138 | + | ||
| 139 | +### 🔧 Configuration | ||
| 140 | + | ||
| 141 | +```yaml | ||
| 142 | +- name: duckduckgo | ||
| 143 | + transport: http | ||
| 144 | + url: https://mcp.hasdata.com/mcp?apis=duckduckgo | ||
| 145 | + auth: | ||
| 146 | + type: api_key | ||
| 147 | + apiKeyEnv: HASDATA_API_KEY | ||
| 148 | + apiKeyHeader: x-api-key | ||
| 149 | +``` | ||
| 150 | + | ||
| 151 | +> ⚠️ **Note importante :** Le serveur DuckDuckGo MCP officiel est hébergé par HasData. | ||
| 152 | +> L'URL `https://duckduckgo.com/mcp` n'est PAS un serveur MCP - c'est juste le site web de DuckDuckGo. | ||
| 153 | +> | ||
| 154 | +> **Test de connectivité :** | ||
| 155 | +> ```bash | ||
| 156 | +> curl -I -H "x-api-key: VOTRE_CLE" https://mcp.hasdata.com/mcp?apis=duckduckgo | ||
| 157 | +> ``` | ||
| 158 | +> Doit retourner HTTP 200 ou 401 (si la clé est invalide). | ||
| 159 | + | ||
| 160 | +### 💡 Utilisation | ||
| 161 | + | ||
| 162 | +Une fois configuré, mini-me aura accès à l'outil : **`hasdata_duckduckgo_serp_getSearchResults`** | ||
| 163 | + | ||
| 164 | +**Exemples de requêtes :** | ||
| 165 | + | ||
| 166 | +``` | ||
| 167 | +"Recherche des dernières nouvelles sur l'IA" | ||
| 168 | +-> Le modèle peut utiliser hasdata_duckduckgo_serp_getSearchResults avec votre requête | ||
| 169 | + | ||
| 170 | +"Quelle est la documentation officielle de Go 1.22 ?" | ||
| 171 | +-> Le modèle peut rechercher et retourner des liens pertinents | ||
| 172 | +``` | ||
| 173 | + | ||
| 174 | +**Paramètres disponibles :** | ||
| 175 | +- `q` (requis) : La requête de recherche | ||
| 176 | +- `kl` : Région au format `<pays>-<langue>` (ex: `fr-fr`, `us-en`, `de-de`) | ||
| 177 | +- `cc` : Code pays à 2 lettres | ||
| 178 | +- `setLang` : Langue de l'interface | ||
| 179 | +- `safeSearch` : `off`, `moderate`, ou `strict` | ||
| 180 | +- `max_results` : Nombre maximum de résultats (par défaut: 10) | ||
| 181 | + | ||
| 182 | +> ⚠️ **Coût :** Chaque appel coûte **10 crédits**. Le tier gratuit offre 1,000 crédits/mois (100 recherches). | ||
| 183 | + | ||
| 184 | +### 🎯 Cas d'usage | ||
| 185 | + | ||
| 186 | +- Recherche de documentation | ||
| 187 | +- Vérification de faits récents | ||
| 188 | +- Trouver des exemples de code | ||
| 189 | +- Résoudre des problèmes avec des solutions en ligne | ||
| 190 | + | ||
| 191 | +### ⚠️ Limitations | ||
| 192 | + | ||
| 193 | +- Une seule tool disponible via HasData: `hasdata_duckduckgo_serp_getSearchResults` | ||
| 194 | + | ||
| 195 | +--- | ||
| 196 | + | ||
| 197 | +## 🐙 GitHub MCP Server | ||
| 198 | + | ||
| 199 | +### 📌 Qu'est-ce que c'est ? | ||
| 200 | + | ||
| 201 | +**GitHub MCP** est le serveur MCP **officiel** de GitHub, qui fournit un accès complet à l'API GitHub. | ||
| 202 | + | ||
| 203 | +- **Authentification** : GitHub Personal Access Token (PAT) requis | ||
| 204 | +- **Transport** : Streamable HTTP | ||
| 205 | +- **Outils** : Accès aux dépôts, code, issues, pull requests, etc. | ||
| 206 | +- **Official** : Maintenu par GitHub | ||
| 207 | + | ||
| 208 | +### 🔧 Configuration | ||
| 209 | + | ||
| 210 | +```yaml | ||
| 211 | +- name: github | ||
| 212 | + transport: http | ||
| 213 | + url: https://api.githubcopilot.com/mcp/ | ||
| 214 | + auth: | ||
| 215 | + type: bearer | ||
| 216 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 217 | + headers: | ||
| 218 | + Accept: "application/json, text/event-stream" | ||
| 219 | +``` | ||
| 220 | + | ||
| 221 | +### 💡 Utilisation | ||
| 222 | + | ||
| 223 | +Une fois configuré, mini-me aura accès aux outils GitHub (préfixés par `github_`). | ||
| 224 | + | ||
| 225 | +**Exemples d'outils typiques :** | ||
| 226 | +- `list_repositories` - Lister vos dépôts | ||
| 227 | +- `read_repository` - Lire le contenu d'un dépôt | ||
| 228 | +- `search_code` - Rechercher dans le code | ||
| 229 | +- `get_issue` - Obtenir une issue | ||
| 230 | +- `list_pull_requests` - Lister les pull requests | ||
| 231 | + | ||
| 232 | +> ⚠️ **Note :** Les outils disponibles dépendent des **scopes** de votre PAT. | ||
| 233 | + | ||
| 234 | +**Exemples de requêtes :** | ||
| 235 | + | ||
| 236 | +``` | ||
| 237 | +"Quels sont mes dépôts publics sur GitHub ?" | ||
| 238 | +-> Le modèle peut utiliser github_list_repositories | ||
| 239 | + | ||
| 240 | +"Montre-moi le fichier README.md de mon dépôt mini-me" | ||
| 241 | +-> Le modèle peut utiliser github_read_repository | ||
| 242 | + | ||
| 243 | +"Recherche l'utilisation de modelcontextprotocol dans mon code" | ||
| 244 | +-> Le modèle peut utiliser github_search_code | ||
| 245 | +``` | ||
| 246 | + | ||
| 247 | +### 🎯 Cas d'usage | ||
| 248 | + | ||
| 249 | +- Explorer vos dépôts GitHub | ||
| 250 | +- Lire des fichiers spécifiques | ||
| 251 | +- Rechercher dans votre code | ||
| 252 | +- Gérer les issues et pull requests | ||
| 253 | +- Analyser la structure de vos projets | ||
| 254 | + | ||
| 255 | +### 🔑 Authentification et Scopes | ||
| 256 | + | ||
| 257 | +Votre PAT doit avoir les **scopes** appropriés pour les outils que vous voulez utiliser : | ||
| 258 | + | ||
| 259 | +| Outils | Scopes requis | | ||
| 260 | +|-------|---------------| | ||
| 261 | +| Lister les dépôts | `repo` (lecture) | | ||
| 262 | +| Lire les fichiers | `repo` (lecture) | | ||
| 263 | +| Rechercher dans le code | `repo` (lecture) | | ||
| 264 | +| Gérer les issues | `repo`, `issues` | | ||
| 265 | +| Gérer les PR | `repo`, `pull_requests` | | ||
| 266 | + | ||
| 267 | +> ✅ **Pour commencer** : Utilisez un PAT avec le scope `repo` (lecture seulement) | ||
| 268 | + | ||
| 269 | +--- | ||
| 270 | + | ||
| 271 | +## 📚 Wikipedia MCP Server | ||
| 272 | + | ||
| 273 | +### 📌 Qu'est-ce que c'est ? | ||
| 274 | + | ||
| 275 | +**Wikipedia MCP** est un serveur MCP hébergé qui fournit un **accès complet à Wikipedia**. | ||
| 276 | + | ||
| 277 | +- **Gratuit** - Aucune clé API nécessaire | ||
| 278 | +- **Complet** - Recherche, articles, résumés, sections, liens, etc. | ||
| 279 | +- **Multi-langues** - Support de nombreuses langues | ||
| 280 | +- **Aucune authentification** requise | ||
| 281 | + | ||
| 282 | +### 🔧 Configuration | ||
| 283 | + | ||
| 284 | +```yaml | ||
| 285 | +- name: wikipedia | ||
| 286 | + transport: http | ||
| 287 | + url: https://wikipedia-mcp.vercel.app/mcp | ||
| 288 | + auth: | ||
| 289 | + type: none | ||
| 290 | + headers: | ||
| 291 | + Accept: "application/json, text/event-stream" | ||
| 292 | +``` | ||
| 293 | + | ||
| 294 | +### 💡 Utilisation | ||
| 295 | + | ||
| 296 | +Une fois configuré, mini-me aura accès aux outils Wikipedia suivants (préfixés par `wikipedia_`) : | ||
| 297 | +- `wikipedia_search_wikipedia` - Rechercher des articles | ||
| 298 | +- `wikipedia_get_article` - Récupérer un article complet | ||
| 299 | +- `wikipedia_get_summary` - Obtenir un résumé | ||
| 300 | +- `wikipedia_get_sections` - Lister les sections d'un article | ||
| 301 | +- `wikipedia_get_links` - Obtenir les liens d'un article | ||
| 302 | +- `wikipedia_get_coordinates` - Coordonnées géographiques | ||
| 303 | +- `wikipedia_get_related_topics` - Sujets liés | ||
| 304 | +- `wikipedia_summarize_article_for_query` - Résumer un article pour une requête | ||
| 305 | +- `wikipedia_summarize_article_section` - Résumer une section | ||
| 306 | +- `wikipedia_extract_key_facts` - Extraire les faits clés | ||
| 307 | +- `wikipedia_test_wikipedia_connectivity` - Tester la connectivité | ||
| 308 | +- `wikipedia_list_supported_countries` - Lister les pays supportés | ||
| 309 | +- `wikipedia_multi_search_wikipedia` - Recherche multiple | ||
| 310 | + | ||
| 311 | +**Exemples de requêtes :** | ||
| 312 | + | ||
| 313 | +``` | ||
| 314 | +"Qu'est-ce que le Model Context Protocol ?" | ||
| 315 | +-> Le modèle peut utiliser wikipedia_search_wikipedia pour trouver l'article | ||
| 316 | + | ||
| 317 | +"Donne-moi un résumé de l'article sur la relativité" | ||
| 318 | +-> Le modèle peut utiliser wikipedia_get_summary | ||
| 319 | + | ||
| 320 | +"Quels sont les liens dans l'article sur l'IA ?" | ||
| 321 | +-> Le modèle peut utiliser wikipedia_get_links | ||
| 322 | +``` | ||
| 323 | + | ||
| 324 | +### 🎯 Cas d'usage | ||
| 325 | + | ||
| 326 | +- Recherche d'informations générales | ||
| 327 | +- Vérification de faits | ||
| 328 | +- Obtenir des définitions et explications | ||
| 329 | +- Explorer des sujets de manière approfondie | ||
| 330 | + | ||
| 331 | +### ✅ Avantages | ||
| 332 | + | ||
| 333 | +- **Gratuit** - Pas de limitations de quota connues | ||
| 334 | +- **Immédiat** - Pas besoin de s'inscrire | ||
| 335 | +- **Complet** - Accès à toutes les fonctionnalités Wikipedia | ||
| 336 | +- Les résultats sont limités par le format de réponse de DuckDuckGo | ||
| 337 | +- Pas de contexte de conversation (chaque recherche est indépendante) | ||
| 338 | + | ||
| 339 | +--- | ||
| 340 | + | ||
| 341 | +## 🎯 Context7 MCP Server | ||
| 342 | + | ||
| 343 | +### 📌 Qu'est-ce que c'est ? | ||
| 344 | + | ||
| 345 | +**Context7** est un serveur MCP qui fournit : | ||
| 346 | +- Recherche dans la documentation officielle | ||
| 347 | +- Compréhension du contexte de code | ||
| 348 | +- Génération de code contextuel | ||
| 349 | + | ||
| 350 | +### 🔑 Authentification Requise | ||
| 351 | + | ||
| 352 | +Context7 nécessite une **clé API**. | ||
| 353 | + | ||
| 354 | +1. **Obtenez une clé API :** | ||
| 355 | + - Allez sur [https://context7.com](https://context7.com) | ||
| 356 | + - Inscrivez-vous (gratuit) | ||
| 357 | + - Récupérez votre clé API dans le tableau de bord | ||
| 358 | + | ||
| 359 | +2. **Exportez la clé :** | ||
| 360 | + ```bash | ||
| 361 | + export CONTEXT7_API_KEY="votre-clé-api" | ||
| 362 | + ``` | ||
| 363 | + | ||
| 364 | +3. **Ajoutez au shell profile (optionnel) :** | ||
| 365 | + ```bash | ||
| 366 | + # Pour bash/zsh | ||
| 367 | + echo 'export CONTEXT7_API_KEY="votre-clé-api"' >> ~/.bashrc | ||
| 368 | + source ~/.bashrc | ||
| 369 | + ``` | ||
| 370 | + | ||
| 371 | +### 🔧 Configuration | ||
| 372 | + | ||
| 373 | +```yaml | ||
| 374 | +- name: context7 | ||
| 375 | + transport: http | ||
| 376 | + url: https://mcp.context7.com/mcp | ||
| 377 | + timeout: 60s # Temps plus long pour les requêtes complexes | ||
| 378 | + auth: | ||
| 379 | + type: bearer | ||
| 380 | + tokenEnv: CONTEXT7_API_KEY | ||
| 381 | +``` | ||
| 382 | + | ||
| 383 | +### 💡 Utilisation | ||
| 384 | + | ||
| 385 | +Une fois configuré, mini-me aura accès aux outils Context7, préfixés par `context7_` : | ||
| 386 | +- Les outils disponibles dépendent de ce que le serveur Context7 expose | ||
| 387 | +- Exemples typiques : `search_documentation`, `get_code_context`, etc. | ||
| 388 | + | ||
| 389 | +**Exemples de requêtes :** | ||
| 390 | + | ||
| 391 | +``` | ||
| 392 | +"Comment utiliser le package http en Go ?" | ||
| 393 | +-> Le modèle peut utiliser context7_search_documentation pour trouver la doc officielle | ||
| 394 | + | ||
| 395 | +"Explique moi ce code React" | ||
| 396 | +-> Le modèle peut utiliser context7_get_code_context pour comprendre le contexte | ||
| 397 | +``` | ||
| 398 | + | ||
| 399 | +### 🎯 Cas d'usage | ||
| 400 | + | ||
| 401 | +- Recherche de documentation technique | ||
| 402 | +- Compréhension de bibliothèques et frameworks | ||
| 403 | +- Génération de code avec contexte | ||
| 404 | +- Debugging avec des exemples réels | ||
| 405 | + | ||
| 406 | +### ⚠️ Limitations | ||
| 407 | + | ||
| 408 | +- Nécessite une connexion internet | ||
| 409 | +- Dépend de la disponibilité du service Context7 | ||
| 410 | +- Les outils disponibles peuvent changer | ||
| 411 | + | ||
| 412 | +--- | ||
| 413 | + | ||
| 414 | +## 🔄 Filtrage des Outils | ||
| 415 | + | ||
| 416 | +Par défaut, **tous les outils** d'un serveur MCP sont exposés à mini-me. | ||
| 417 | + | ||
| 418 | +Vous pouvez **filtrer** les outils en spécifiant une liste : | ||
| 419 | + | ||
| 420 | +```yaml | ||
| 421 | +- name: context7 | ||
| 422 | + transport: http | ||
| 423 | + url: https://mcp.context7.com/mcp | ||
| 424 | + auth: | ||
| 425 | + type: bearer | ||
| 426 | + tokenEnv: CONTEXT7_API_KEY | ||
| 427 | + tools: # Seuls ces outils seront exposés | ||
| 428 | + - search_documentation | ||
| 429 | + - get_code_context | ||
| 430 | +``` | ||
| 431 | + | ||
| 432 | +Cela réduit : | ||
| 433 | +- Le nombre d'outils dans le prompt (économie de tokens) | ||
| 434 | +- La surface d'attaque potentielle | ||
| 435 | +- Le bruit dans la liste des outils | ||
| 436 | + | ||
| 437 | +--- | ||
| 438 | + | ||
| 439 | +## 🛡️ Sécurité | ||
| 440 | + | ||
| 441 | +### Permissions | ||
| 442 | + | ||
| 443 | +Deux modes de permissions sont disponibles : | ||
| 444 | + | ||
| 445 | +| Mode | Comportement | Recommandation | | ||
| 446 | +|------|-------------|----------------| | ||
| 447 | +| `ask` | Demande confirmation avant chaque appel MCP | ✅ Recommandé | | ||
| 448 | +| `auto` | Exécute automatiquement (comme bash) | ⚠️ À utiliser avec prudence | | ||
| 449 | + | ||
| 450 | +**Configuration globale :** | ||
| 451 | +```yaml | ||
| 452 | +mcp: | ||
| 453 | + permissions: ask # Mode par défaut pour tous les serveurs | ||
| 454 | +``` | ||
| 455 | + | ||
| 456 | +**Override par serveur :** | ||
| 457 | +```yaml | ||
| 458 | +servers: | ||
| 459 | + - name: duckduckgo | ||
| 460 | + permissions: auto # Auto pour ce serveur spécifique | ||
| 461 | + ... | ||
| 462 | + - name: context7 | ||
| 463 | + permissions: ask # Ask pour ce serveur | ||
| 464 | + ... | ||
| 465 | +``` | ||
| 466 | + | ||
| 467 | +### Bonnes Pratiques | ||
| 468 | + | ||
| 469 | +1. **Utilisez toujours `permissions: ask`** pour les serveurs que vous ne connaissez pas | ||
| 470 | +2. **Ne partagez pas vos clés API** dans les fichiers de configuration | ||
| 471 | +3. **Utilisez des variables d'environnement** pour les clés sensibles | ||
| 472 | +4. **Filtrez les outils** que vous n'utilisez pas | ||
| 473 | +5. **Testez en local** avant de déployer en production | ||
| 474 | + | ||
| 475 | +--- | ||
| 476 | + | ||
| 477 | +## 🛠️ Dépannage | ||
| 478 | + | ||
| 479 | +### Problème : "mcp: enabled but no servers configured" | ||
| 480 | + | ||
| 481 | +**Cause :** MCP est activé mais aucun serveur n'est configuré. | ||
| 482 | + | ||
| 483 | +**Solution :** Ajoutez au moins un serveur dans la liste `servers`. | ||
| 484 | + | ||
| 485 | +--- | ||
| 486 | + | ||
| 487 | +### Problème : "mcp server X: discovery failed: connection refused" | ||
| 488 | + | ||
| 489 | +**Causes possibles :** | ||
| 490 | +- Le serveur MCP est hors ligne | ||
| 491 | +- L'URL est incorrecte | ||
| 492 | +- Problème de réseau | ||
| 493 | +- Authentification manquante ou incorrecte | ||
| 494 | +- Headers HTTP manquants (comme `Accept: application/json, text/event-stream`) | ||
| 495 | + | ||
| 496 | +**Solutions :** | ||
| 497 | +- Vérifiez l'URL du serveur | ||
| 498 | +- Vérifiez que les variables d'environnement pour l'authentification sont définies | ||
| 499 | +- Pour Context7, assurez-vous que le header `Accept` est correctement configuré | ||
| 500 | +- Testez la connectivité : `curl -v -H "Authorization: Bearer VOTRE_TOKEN" -H "Accept: application/json, text/event-stream" https://mcp.context7.com/mcp` | ||
| 501 | +- Essayez plus tard | ||
| 502 | + | ||
| 503 | +--- | ||
| 504 | + | ||
| 505 | +### Problème : "missing bearer token for env var CONTEXT7_API_KEY" | ||
| 506 | + | ||
| 507 | +**Cause :** La variable d'environnement `CONTEXT7_API_KEY` n'est pas définie. | ||
| 508 | + | ||
| 509 | +**Solution :** | ||
| 510 | +```bash | ||
| 511 | +export CONTEXT7_API_KEY="votre-clé" | ||
| 512 | +./mm -config demo/agent.dmr.mcp.yaml | ||
| 513 | +``` | ||
| 514 | + | ||
| 515 | +--- | ||
| 516 | + | ||
| 517 | +### Problème : "missing api_key for env var HASDATA_API_KEY" | ||
| 518 | + | ||
| 519 | +**Cause :** La variable d'environnement `HASDATA_API_KEY` n'est pas définie. | ||
| 520 | + | ||
| 521 | +**Solution :** | ||
| 522 | +```bash | ||
| 523 | +export HASDATA_API_KEY="votre-clé" | ||
| 524 | +./mm -config demo/agent.dmr.mcp.yaml | ||
| 525 | +``` | ||
| 526 | + | ||
| 527 | +> ✅ **Comment obtenir une clé HasData :** | ||
| 528 | +> 1. Inscrivez-vous gratuitement sur [https://app.hasdata.com](https://app.hasdata.com) | ||
| 529 | +> 2. Récupérez votre clé API dans le tableau de bord | ||
| 530 | +> 3. Vous obtenez **1,000 crédits gratuits par mois** (100 recherches) | ||
| 531 | + | ||
| 532 | +--- | ||
| 533 | + | ||
| 534 | +### Problème : Les outils MCP n'apparaissent pas dans la bannière | ||
| 535 | + | ||
| 536 | +**Causes possibles :** | ||
| 537 | +- MCP est désactivé | ||
| 538 | +- La discovery a échoué silencieusement | ||
| 539 | +- Tous les outils ont été filtrés (binary I/O) | ||
| 540 | + | ||
| 541 | +**Solution :** | ||
| 542 | +- Vérifiez les warnings dans la bannière de démarrage | ||
| 543 | +- Vérifiez que `mcp.enabled: true` | ||
| 544 | +- Essayez avec un timeout de discovery plus long | ||
| 545 | + | ||
| 546 | +--- | ||
| 547 | + | ||
| 548 | +## 📊 Exemples Complets | ||
| 549 | + | ||
| 550 | +### Configuration Minimale (DuckDuckGo seul) | ||
| 551 | + | ||
| 552 | +```yaml | ||
| 553 | +mcp: | ||
| 554 | + enabled: true | ||
| 555 | + permissions: ask | ||
| 556 | + servers: | ||
| 557 | + - name: duckduckgo | ||
| 558 | + transport: http | ||
| 559 | + url: https://mcp.hasdata.com/mcp?apis=duckduckgo | ||
| 560 | + auth: | ||
| 561 | + type: api_key | ||
| 562 | + apiKeyEnv: HASDATA_API_KEY | ||
| 563 | + apiKeyHeader: x-api-key | ||
| 564 | +``` | ||
| 565 | + | ||
| 566 | +### Configuration Complète (Les deux serveurs) | ||
| 567 | + | ||
| 568 | +Voir `demo/agent.dmr.mcp.yaml` pour un exemple complet. | ||
| 569 | + | ||
| 570 | +--- | ||
| 571 | + | ||
| 572 | +## 🎓 Astuces | ||
| 573 | + | ||
| 574 | +### 1. Nom des Outils | ||
| 575 | + | ||
| 576 | +Les outils MCP sont **préfixés avec le nom du serveur** : | ||
| 577 | +- `duckduckgo_search` | ||
| 578 | +- `context7_search_documentation` | ||
| 579 | + | ||
| 580 | +Cela évite les collisions et permet de savoir d'où vient chaque outil. | ||
| 581 | + | ||
| 582 | +### 2. Découverte au Démarrage | ||
| 583 | + | ||
| 584 | +La découverte des outils MCP se fait **au démarrage** de mini-me, pas à la première utilisation. | ||
| 585 | +Cela signifie : | ||
| 586 | +- Un temps de démarrage légèrement plus long | ||
| 587 | +- La liste des outils est **statique** pour la session | ||
| 588 | +- Si un serveur meurt pendant la session, les outils deviennent indisponibles | ||
| 589 | + | ||
| 590 | +### 3. Texte Seulement | ||
| 591 | + | ||
| 592 | +Mini-me **ignore les outils binaires**. Seuls les outils avec entrée/sortie texte sont exposés. | ||
| 593 | + | ||
| 594 | +Cela signifie que les outils qui manipulent des images, PDFs, ou autres données binaires ne seront **pas disponibles**. | ||
| 595 | + | ||
| 596 | +--- | ||
| 597 | + | ||
| 598 | +## 📚 Références | ||
| 599 | + | ||
| 600 | +- [Context7 Official Site](https://context7.com) | ||
| 601 | +- [DuckDuckGo](https://duckduckgo.com) | ||
| 602 | +- [HasData DuckDuckGo MCP Server](https://github.com/HasData/duckduckgo-mcp) | ||
| 603 | +- [HasData Dashboard](https://app.hasdata.com) | ||
| 604 | +- [MCP Specification](https://modelcontextprotocol.io/) | ||
| 605 | + | ||
| 606 | +--- | ||
| 607 | + | ||
| 608 | +## 🔄 Changelog | ||
| 609 | + | ||
| 610 | +| Date | Changement | | ||
| 611 | +|------|------------| | ||
| 612 | +| 2026-09-23 | Guide initial créé | | ||
| 613 | +| 2026-09-23 | Configuration MCP ajoutée à agent.dmr.mcp.yaml | | ||
| 614 | +| 2026-09-23 | Mise à jour : DuckDuckGo utilise maintenant le serveur HasData (https://mcp.hasdata.com) | | ||
| 615 | +| 2026-09-23 | Ajout du header Accept pour Context7 | | ||
| 616 | +| 2026-09-23 | Correction des URLs et authentifications | | ||
| 617 | + | ||
| 618 | +--- | ||
| 619 | + | ||
| 620 | +## 💬 Support | ||
| 621 | + | ||
| 622 | +Si vous rencontrez des problèmes avec les serveurs MCP : | ||
| 623 | +1. Vérifiez les warnings dans la bannière de mini-me | ||
| 624 | +2. Consultez ce guide | ||
| 625 | +3. Vérifiez les logs et les messages d'erreur | ||
| 626 | +4. Ouvrez un issue avec les détails du problème | ||
| 627 | + | ||
| 628 | +--- | ||
| 629 | + | ||
| 630 | +**Note :** Les serveurs MCP sont des services tiers. Leur disponibilité, leurs outils et leur comportement peuvent changer sans préavis. Mini-me fait de son mieux pour intégrer ces services de manière sûre et fiable, mais ne peut pas garantir leur fonctionnement. | ||
modified
demo/README.md +35 -0 | @@ -4,6 +4,41 @@ | ||
| 4 | 4 | |
| 5 | 5 | [Insert a detailed explanation of what Mini Me is here.] |
| 6 | 6 | |
| 7 | +## MCP (Model Context Protocol) Servers | |
| 8 | + | |
| 9 | +Mini-me supports MCP servers for extended capabilities like web search and documentation lookup. | |
| 10 | + | |
| 11 | +See **[MCP_SERVERS_GUIDE.md](./MCP_SERVERS_GUIDE.md)** for complete setup instructions. | |
| 12 | + | |
| 13 | +### Quick Start | |
| 14 | + | |
| 15 | +1. Export your API keys: | |
| 16 | + ```bash | |
| 17 | + export CONTEXT7_API_KEY="your-context7-api-key" | |
| 18 | + export GITHUB_PERSONAL_ACCESS_TOKEN="your-github-pat" | |
| 19 | + ``` | |
| 20 | + > Note: Wikipedia doesn't require authentication | |
| 21 | + | |
| 22 | +2. Run mini-me with MCP configuration: | |
| 23 | + ```bash | |
| 24 | + ./mm -config demo/agent.dmr.mcp.yaml | |
| 25 | + ``` | |
| 26 | + | |
| 27 | +3. Ask the model to use MCP tools: | |
| 28 | + - `"What does this React code do?"` → Uses `context7_resolve_library_id` and `context7_query_docs` | |
| 29 | + - `"Tell me about the Model Context Protocol"` → Uses `wikipedia_search_wikipedia` | |
| 30 | + - `"Summarize the article on Artificial Intelligence"` → Uses `wikipedia_get_summary` | |
| 31 | + - `"List my GitHub repositories"` → Uses `github_list_repositories` | |
| 32 | + - `"Show me the README from my repo"` → Uses `github_read_repository` | |
| 33 | + | |
| 34 | +### Configured Servers | |
| 35 | + | |
| 36 | +The `agent.dmr.mcp.yaml` file includes configuration for: | |
| 37 | +- **Context7 MCP** - Documentation and code context (requires CONTEXT7_API_KEY) | |
| 38 | +- **GitHub MCP** - Full GitHub access (requires GITHUB_PERSONAL_ACCESS_TOKEN) | |
| 39 | +- **Wikipedia MCP** - Full Wikipedia access (no authentication required) | |
| 40 | +- **DuckDuckGo MCP (HasData)** - Web search via HasData (requires HASDATA_API_KEY, 1,000 free credits/month = 100 searches) - *currently commented out* | |
| 41 | + | |
| 7 | 42 | ## License |
| 8 | 43 | |
| 9 | 44 | This project is licensed under the WTF License. |
| @@ -4,6 +4,41 @@ | |||
| 4 | 4 | ||
| 5 | [Insert a detailed explanation of what Mini Me is here.] | 5 | [Insert a detailed explanation of what Mini Me is here.] |
| 6 | 6 | ||
| 7 | +## MCP (Model Context Protocol) Servers | ||
| 8 | + | ||
| 9 | +Mini-me supports MCP servers for extended capabilities like web search and documentation lookup. | ||
| 10 | + | ||
| 11 | +See **[MCP_SERVERS_GUIDE.md](./MCP_SERVERS_GUIDE.md)** for complete setup instructions. | ||
| 12 | + | ||
| 13 | +### Quick Start | ||
| 14 | + | ||
| 15 | +1. Export your API keys: | ||
| 16 | + ```bash | ||
| 17 | + export CONTEXT7_API_KEY="your-context7-api-key" | ||
| 18 | + export GITHUB_PERSONAL_ACCESS_TOKEN="your-github-pat" | ||
| 19 | + ``` | ||
| 20 | + > Note: Wikipedia doesn't require authentication | ||
| 21 | + | ||
| 22 | +2. Run mini-me with MCP configuration: | ||
| 23 | + ```bash | ||
| 24 | + ./mm -config demo/agent.dmr.mcp.yaml | ||
| 25 | + ``` | ||
| 26 | + | ||
| 27 | +3. Ask the model to use MCP tools: | ||
| 28 | + - `"What does this React code do?"` → Uses `context7_resolve_library_id` and `context7_query_docs` | ||
| 29 | + - `"Tell me about the Model Context Protocol"` → Uses `wikipedia_search_wikipedia` | ||
| 30 | + - `"Summarize the article on Artificial Intelligence"` → Uses `wikipedia_get_summary` | ||
| 31 | + - `"List my GitHub repositories"` → Uses `github_list_repositories` | ||
| 32 | + - `"Show me the README from my repo"` → Uses `github_read_repository` | ||
| 33 | + | ||
| 34 | +### Configured Servers | ||
| 35 | + | ||
| 36 | +The `agent.dmr.mcp.yaml` file includes configuration for: | ||
| 37 | +- **Context7 MCP** - Documentation and code context (requires CONTEXT7_API_KEY) | ||
| 38 | +- **GitHub MCP** - Full GitHub access (requires GITHUB_PERSONAL_ACCESS_TOKEN) | ||
| 39 | +- **Wikipedia MCP** - Full Wikipedia access (no authentication required) | ||
| 40 | +- **DuckDuckGo MCP (HasData)** - Web search via HasData (requires HASDATA_API_KEY, 1,000 free credits/month = 100 searches) - *currently commented out* | ||
| 41 | + | ||
| 7 | ## License | 42 | ## License |
| 8 | 43 | ||
| 9 | This project is licensed under the WTF License. | 44 | This project is licensed under the WTF License. |
added
demo/agent.dmr.mcp.yaml +264 -0 | new file mode 100644 | ||
| @@ -0,0 +1,264 @@ | ||
| 1 | +# Agent settings. | |
| 2 | +# | |
| 3 | +provider: dmr | |
| 4 | + | |
| 5 | +model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M | |
| 6 | +#model: huggingface.co/unsloth/gemma-4-e2b-it-gguf:Q4_K_M | |
| 7 | +#model: huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M | |
| 8 | + | |
| 9 | +baseUrl: http://localhost:12434/engines/v1 | |
| 10 | +fallback: http://host.docker.internal:12434/engines/v1 | |
| 11 | + | |
| 12 | + | |
| 13 | +# How many tokens the server actually serves. | |
| 14 | +# 0 = ask the server | |
| 15 | +contextWindow: 0 | |
| 16 | + | |
| 17 | +# Max number of characters a tool returns to the model. | |
| 18 | +# Context safeguard: beyond that, the output is truncated (beginning + end kept). | |
| 19 | +maxOutput: 16000 | |
| 20 | + | |
| 21 | +# Max number of model <-> tools round trips for a single question. | |
| 22 | +# Raise it for tasks that need long chains of commands. | |
| 23 | +maxTurns: 40 | |
| 24 | + | |
| 25 | +# The `bash` tool. | |
| 26 | +# `false` removes it: the model can still read and edit files (editTools) and load skills, | |
| 27 | +# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool". | |
| 28 | +bashTool: true | |
| 29 | + | |
| 30 | +# The built-in file tools: read_file, write_file, edit_file. | |
| 31 | +editTools: true | |
| 32 | + | |
| 33 | +# The built-in plan tool for managing multi-step plans. | |
| 34 | +# Set to false to disable plan creation and tracking via the `plan` tool. | |
| 35 | +planTool: true | |
| 36 | + | |
| 37 | +lsp: | |
| 38 | + servers: | |
| 39 | + - name: gopls # go install golang.org/x/tools/gopls@latest | |
| 40 | + command: gopls | |
| 41 | + extensions: [".go"] | |
| 42 | + | |
| 43 | +# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename). | |
| 44 | +# The file extension picks the server. A server missing from the PATH is skipped with a warning. | |
| 45 | +# No server listed = no `lsp` tool. Examples (uncomment what you have installed): | |
| 46 | +# lsp: | |
| 47 | +# servers: | |
| 48 | +# - name: gopls | |
| 49 | +# command: gopls | |
| 50 | +# extensions: [".go"] | |
| 51 | +# - name: typescript | |
| 52 | +# command: typescript-language-server | |
| 53 | +# args: ["--stdio"] | |
| 54 | +# extensions: [".ts", ".tsx", ".js", ".jsx"] | |
| 55 | +# - name: python | |
| 56 | +# command: pyright-langserver | |
| 57 | +# args: ["--stdio"] | |
| 58 | +# extensions: [".py"] | |
| 59 | +# - name: rust | |
| 60 | +# command: rust-analyzer | |
| 61 | +# extensions: [".rs"] | |
| 62 | + | |
| 63 | +# Where the `read_skill` tool looks for its markdown procedures. | |
| 64 | +# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills. | |
| 65 | +skillsDir: skills | |
| 66 | + | |
| 67 | +# Persistent sessions, per project. | |
| 68 | +# When enabled, every conversation is saved as one JSON file under `dir` | |
| 69 | +sessions: | |
| 70 | + enabled: true | |
| 71 | + dir: .mm/sessions | |
| 72 | + | |
| 73 | +# How many lines of a command's output are echoed to the terminal. | |
| 74 | +# 0 disables it. | |
| 75 | +previewLines: 20 | |
| 76 | + | |
| 77 | +# The system prompt: what the agent is, and what it is allowed to do. | |
| 78 | +# This is the knob to play with — it is the shortest path to changing behaviour. | |
| 79 | +system: | | |
| 80 | + Your name is Bob The Bot. | |
| 81 | + You are a coding agent working in a terminal. | |
| 82 | + You have a "bash" tool to run shell commands. | |
| 83 | + Use it to explore files, run tests, inspect the repository, etc. | |
| 84 | + Chain several commands if needed, then answer clearly in English. | |
| 85 | + | |
| 86 | + A request often mixes things you answer from yourself ("say hello") with | |
| 87 | + things only a command can answer ("list the files"). Handle every part, in | |
| 88 | + the order asked, and run a command for each part that needs one. | |
| 89 | + Never state the contents of a file, the output of a command, or the state of | |
| 90 | + the repository unless a command in THIS answer returned it. What you did not | |
| 91 | + read, you do not know: run the command instead of recalling it. | |
| 92 | + | |
| 93 | + SKILLS | |
| 94 | + You have a second tool, `read_skill`. Its description lists the procedures | |
| 95 | + available for this project — one per kind of task. | |
| 96 | + | |
| 97 | + Any request to DO something to a Go project is a skill, not a shell command | |
| 98 | + you invent. Match the request against that list, call `read_skill` FIRST, | |
| 99 | + before any bash command, and then follow what it says step by step. | |
| 100 | + | |
| 101 | + FILE EDITING | |
| 102 | + You have three tools for files: `read_file`, `edit_file` and `write_file`. | |
| 103 | + They are how a file gets read and changed here: each change is exact, | |
| 104 | + checked before it is written, and comes back as a diff with line numbers. | |
| 105 | + bash is for running things — building, testing, listing, searching. | |
| 106 | + | |
| 107 | + - Read before you write: call `read_file` on the file (numbered=true when | |
| 108 | + you need line numbers). You cannot target text you have not seen; never | |
| 109 | + rely on what you think you remember about a file. | |
| 110 | + - To change an existing file, call `edit_file` with one or more {old, new} | |
| 111 | + pairs. `old` is copied from the file character for character — same | |
| 112 | + spaces, same indentation, same line breaks — and appears exactly once: | |
| 113 | + add the surrounding lines until it is unique. Several pairs are applied | |
| 114 | + together, against the original file. An empty `new` deletes the text. | |
| 115 | + - Call `write_file` only to create a file, or to rewrite one entirely and | |
| 116 | + on purpose. On an existing file it replaces everything, including what | |
| 117 | + you did not intend to touch. | |
| 118 | + - Read the diff the tool returns: it says exactly what changed and on which | |
| 119 | + line. If `edit_file` refuses — text not found, ambiguous, overlapping | |
| 120 | + edits — read the file again and fix `old`. Do not fall back to | |
| 121 | + `write_file` to force the change through. | |
| 122 | + - After editing code, run the narrowest check with bash: the formatter, the | |
| 123 | + compiler, or the test covering that file. | |
| 124 | + | |
| 125 | + RULES | |
| 126 | + - Keep everything the file already does, unless the user asked to remove it. | |
| 127 | + - Touch only the files the request is about. Do not add tests, files or | |
| 128 | + features that were not asked for. | |
| 129 | + - Never run a git command unless the user says git, commit or push. | |
| 130 | + - Never move, rename or delete a file unless the user asked for it. | |
| 131 | + - Then answer in English, in a few lines. | |
| 132 | + - If you don't know how to use a <cli>, run `<cli> --help` (or `<cli> help`) | |
| 133 | + to understand the options, then run the command. | |
| 134 | + | |
| 135 | + BACKGROUND JOBS | |
| 136 | + Never let a command block the answer. Anything that serves, watches or runs | |
| 137 | + long goes to the background, with BOTH streams redirected and its pid kept: | |
| 138 | + | |
| 139 | + nohup <command> > /tmp/<job>.log 2>&1 & echo $! > /tmp/<job>.pid | |
| 140 | + | |
| 141 | + Redirecting only stdout still blocks until the process exits. Read the | |
| 142 | + `bg-jobs` skill before you wait on, inspect or stop such a job — each has a | |
| 143 | + rule you cannot guess. Stop every job you started before you finish, and say | |
| 144 | + which ones you left running. | |
| 145 | + | |
| 146 | +# Generation settings (OpenAI API keys). Low temperature for a coding agent: | |
| 147 | +# we want precise and reproducible answers, not creativity. | |
| 148 | +sampling: | |
| 149 | + temperature: 0.0 | |
| 150 | + parallel_tool_calls: false | |
| 151 | + top_p: 0.9 | |
| 152 | + max_tokens: 4096 | |
| 153 | + | |
| 154 | +watchdogTimeout: 30s | |
| 155 | + | |
| 156 | +# Context compression | |
| 157 | +context: | |
| 158 | + enabled: false | |
| 159 | + | |
| 160 | + # Compress when the history reaches this share of the window. | |
| 161 | + threshold: 75 | |
| 162 | + | |
| 163 | + # Fallback on the message count, for when the window is unknown. | |
| 164 | + # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history. | |
| 165 | + maxMessages: 80 | |
| 166 | + | |
| 167 | + # How many recent questions (with their commands and answers) are kept as-is instead of being summarised. | |
| 168 | + # The model works on the last few turns, so summarising them would make it redo what it just did. | |
| 169 | + keepLastTurns: 3 | |
| 170 | + | |
| 171 | + # max_tokens of the summary request. | |
| 172 | + summaryMaxTokens: 1200 | |
| 173 | + | |
| 174 | + # Replaces the built-in summary prompt when set. | |
| 175 | + # Empty = built-in. | |
| 176 | + prompt: "" | |
| 177 | + | |
| 178 | + showStats: true | |
| 179 | + | |
| 180 | +displayCommands: true | |
| 181 | +# Show what a thinking model reasons about before it answers (dim italic in the | |
| 182 | +# terminal, its own style in the window, a thought chunk in an editor). | |
| 183 | +# The reasoning is always READ — it is what keeps the watchdog alive while the | |
| 184 | +# model thinks — this only says whether to show it. | |
| 185 | +showThinking: true | |
| 186 | + | |
| 187 | +# MCP (Model Context Protocol) Client Configuration | |
| 188 | +# Enables access to remote MCP servers for additional tools | |
| 189 | +mcp: | |
| 190 | + enabled: true | |
| 191 | + permissions: ask # Ask before executing MCP tools (recommended for security) | |
| 192 | + discoveryTimeout: 10s # Timeout for discovering tools at startup | |
| 193 | + | |
| 194 | + servers: | |
| 195 | + # DuckDuckGo MCP Server - Hosted by HasData (COMMENTED OUT PER USER REQUEST) | |
| 196 | + # Provides web search capabilities. Requires HASDATA_API_KEY. | |
| 197 | + # Free tier: 1,000 credits/month (100 searches) | |
| 198 | + # - name: duckduckgo | |
| 199 | + # transport: http | |
| 200 | + # url: https://mcp.hasdata.com/mcp?apis=duckduckgo | |
| 201 | + # timeout: 30s | |
| 202 | + # auth: | |
| 203 | + # type: api_key | |
| 204 | + # apiKeyEnv: HASDATA_API_KEY | |
| 205 | + # apiKeyHeader: x-api-key | |
| 206 | + # # Optional: filter which tools to expose | |
| 207 | + # # tools: | |
| 208 | + # # - hasdata_duckduckgo_serp_getSearchResults | |
| 209 | + | |
| 210 | + # Context7 MCP Server - Provides documentation and code context | |
| 211 | + # Requires authentication via API key | |
| 212 | + - name: context7 | |
| 213 | + transport: http | |
| 214 | + url: https://mcp.context7.com/mcp | |
| 215 | + timeout: 60s # Longer timeout for potentially complex queries | |
| 216 | + auth: | |
| 217 | + type: bearer | |
| 218 | + tokenEnv: CONTEXT7_API_KEY # Set this environment variable with your API key | |
| 219 | + headers: | |
| 220 | + Accept: "application/json, text/event-stream" | |
| 221 | + # Optional: filter which tools to expose | |
| 222 | + # tools: | |
| 223 | + # - resolve-library-id | |
| 224 | + # - query-docs | |
| 225 | + | |
| 226 | + # Wikipedia MCP Server - Free Wikipedia access, no authentication required | |
| 227 | + - name: wikipedia | |
| 228 | + transport: http | |
| 229 | + url: https://wikipedia-mcp.vercel.app/mcp | |
| 230 | + timeout: 30s | |
| 231 | + auth: | |
| 232 | + type: none # No authentication required | |
| 233 | + headers: | |
| 234 | + Accept: "application/json, text/event-stream" | |
| 235 | + # Optional: filter which tools to expose | |
| 236 | + # tools: | |
| 237 | + # - search | |
| 238 | + # - get_article | |
| 239 | + | |
| 240 | + # GitHub MCP Server - Official GitHub MCP server | |
| 241 | + # Requires GitHub Personal Access Token (PAT) with appropriate scopes | |
| 242 | + # Get a PAT from: https://github.com/settings/tokens | |
| 243 | + # Required scopes depend on which tools you want to use | |
| 244 | + - name: github | |
| 245 | + transport: http | |
| 246 | + url: https://api.githubcopilot.com/mcp/ | |
| 247 | + timeout: 30s | |
| 248 | + auth: | |
| 249 | + type: bearer | |
| 250 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN # Set this environment variable with your GitHub PAT | |
| 251 | + headers: | |
| 252 | + Accept: "application/json, text/event-stream" | |
| 253 | + # Optional: filter which tools to expose (uncomment to limit tools) | |
| 254 | + tools: | |
| 255 | + - get_me | |
| 256 | + - list_repositories | |
| 257 | + - list_issues | |
| 258 | + - list_pull_requests | |
| 259 | + - get_file_contents | |
| 260 | + - search_code | |
| 261 | + - search_repositories | |
| 262 | + - create_pull_request | |
| 263 | + - add_issue_comment | |
| 264 | + # curl -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" https://api.github.com/user | |
| new file mode 100644 | |||
| @@ -0,0 +1,264 @@ | |||
| 1 | +# Agent settings. | ||
| 2 | +# | ||
| 3 | +provider: dmr | ||
| 4 | + | ||
| 5 | +model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M | ||
| 6 | +#model: huggingface.co/unsloth/gemma-4-e2b-it-gguf:Q4_K_M | ||
| 7 | +#model: huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M | ||
| 8 | + | ||
| 9 | +baseUrl: http://localhost:12434/engines/v1 | ||
| 10 | +fallback: http://host.docker.internal:12434/engines/v1 | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +# How many tokens the server actually serves. | ||
| 14 | +# 0 = ask the server | ||
| 15 | +contextWindow: 0 | ||
| 16 | + | ||
| 17 | +# Max number of characters a tool returns to the model. | ||
| 18 | +# Context safeguard: beyond that, the output is truncated (beginning + end kept). | ||
| 19 | +maxOutput: 16000 | ||
| 20 | + | ||
| 21 | +# Max number of model <-> tools round trips for a single question. | ||
| 22 | +# Raise it for tasks that need long chains of commands. | ||
| 23 | +maxTurns: 40 | ||
| 24 | + | ||
| 25 | +# The `bash` tool. | ||
| 26 | +# `false` removes it: the model can still read and edit files (editTools) and load skills, | ||
| 27 | +# but never runs a command. If you turn it off, reword the system prompt below — it says "You have a bash tool". | ||
| 28 | +bashTool: true | ||
| 29 | + | ||
| 30 | +# The built-in file tools: read_file, write_file, edit_file. | ||
| 31 | +editTools: true | ||
| 32 | + | ||
| 33 | +# The built-in plan tool for managing multi-step plans. | ||
| 34 | +# Set to false to disable plan creation and tracking via the `plan` tool. | ||
| 35 | +planTool: true | ||
| 36 | + | ||
| 37 | +lsp: | ||
| 38 | + servers: | ||
| 39 | + - name: gopls # go install golang.org/x/tools/gopls@latest | ||
| 40 | + command: gopls | ||
| 41 | + extensions: [".go"] | ||
| 42 | + | ||
| 43 | +# Language servers for the `lsp` tool (definition, references, hover, symbols, diagnostics, rename). | ||
| 44 | +# The file extension picks the server. A server missing from the PATH is skipped with a warning. | ||
| 45 | +# No server listed = no `lsp` tool. Examples (uncomment what you have installed): | ||
| 46 | +# lsp: | ||
| 47 | +# servers: | ||
| 48 | +# - name: gopls | ||
| 49 | +# command: gopls | ||
| 50 | +# extensions: [".go"] | ||
| 51 | +# - name: typescript | ||
| 52 | +# command: typescript-language-server | ||
| 53 | +# args: ["--stdio"] | ||
| 54 | +# extensions: [".ts", ".tsx", ".js", ".jsx"] | ||
| 55 | +# - name: python | ||
| 56 | +# command: pyright-langserver | ||
| 57 | +# args: ["--stdio"] | ||
| 58 | +# extensions: [".py"] | ||
| 59 | +# - name: rust | ||
| 60 | +# command: rust-analyzer | ||
| 61 | +# extensions: [".rs"] | ||
| 62 | + | ||
| 63 | +# Where the `read_skill` tool looks for its markdown procedures. | ||
| 64 | +# The path is relative to the CURRENT DIRECTORY, so run the agent from the directory that holds skills. | ||
| 65 | +skillsDir: skills | ||
| 66 | + | ||
| 67 | +# Persistent sessions, per project. | ||
| 68 | +# When enabled, every conversation is saved as one JSON file under `dir` | ||
| 69 | +sessions: | ||
| 70 | + enabled: true | ||
| 71 | + dir: .mm/sessions | ||
| 72 | + | ||
| 73 | +# How many lines of a command's output are echoed to the terminal. | ||
| 74 | +# 0 disables it. | ||
| 75 | +previewLines: 20 | ||
| 76 | + | ||
| 77 | +# The system prompt: what the agent is, and what it is allowed to do. | ||
| 78 | +# This is the knob to play with — it is the shortest path to changing behaviour. | ||
| 79 | +system: | | ||
| 80 | + Your name is Bob The Bot. | ||
| 81 | + You are a coding agent working in a terminal. | ||
| 82 | + You have a "bash" tool to run shell commands. | ||
| 83 | + Use it to explore files, run tests, inspect the repository, etc. | ||
| 84 | + Chain several commands if needed, then answer clearly in English. | ||
| 85 | + | ||
| 86 | + A request often mixes things you answer from yourself ("say hello") with | ||
| 87 | + things only a command can answer ("list the files"). Handle every part, in | ||
| 88 | + the order asked, and run a command for each part that needs one. | ||
| 89 | + Never state the contents of a file, the output of a command, or the state of | ||
| 90 | + the repository unless a command in THIS answer returned it. What you did not | ||
| 91 | + read, you do not know: run the command instead of recalling it. | ||
| 92 | + | ||
| 93 | + SKILLS | ||
| 94 | + You have a second tool, `read_skill`. Its description lists the procedures | ||
| 95 | + available for this project — one per kind of task. | ||
| 96 | + | ||
| 97 | + Any request to DO something to a Go project is a skill, not a shell command | ||
| 98 | + you invent. Match the request against that list, call `read_skill` FIRST, | ||
| 99 | + before any bash command, and then follow what it says step by step. | ||
| 100 | + | ||
| 101 | + FILE EDITING | ||
| 102 | + You have three tools for files: `read_file`, `edit_file` and `write_file`. | ||
| 103 | + They are how a file gets read and changed here: each change is exact, | ||
| 104 | + checked before it is written, and comes back as a diff with line numbers. | ||
| 105 | + bash is for running things — building, testing, listing, searching. | ||
| 106 | + | ||
| 107 | + - Read before you write: call `read_file` on the file (numbered=true when | ||
| 108 | + you need line numbers). You cannot target text you have not seen; never | ||
| 109 | + rely on what you think you remember about a file. | ||
| 110 | + - To change an existing file, call `edit_file` with one or more {old, new} | ||
| 111 | + pairs. `old` is copied from the file character for character — same | ||
| 112 | + spaces, same indentation, same line breaks — and appears exactly once: | ||
| 113 | + add the surrounding lines until it is unique. Several pairs are applied | ||
| 114 | + together, against the original file. An empty `new` deletes the text. | ||
| 115 | + - Call `write_file` only to create a file, or to rewrite one entirely and | ||
| 116 | + on purpose. On an existing file it replaces everything, including what | ||
| 117 | + you did not intend to touch. | ||
| 118 | + - Read the diff the tool returns: it says exactly what changed and on which | ||
| 119 | + line. If `edit_file` refuses — text not found, ambiguous, overlapping | ||
| 120 | + edits — read the file again and fix `old`. Do not fall back to | ||
| 121 | + `write_file` to force the change through. | ||
| 122 | + - After editing code, run the narrowest check with bash: the formatter, the | ||
| 123 | + compiler, or the test covering that file. | ||
| 124 | + | ||
| 125 | + RULES | ||
| 126 | + - Keep everything the file already does, unless the user asked to remove it. | ||
| 127 | + - Touch only the files the request is about. Do not add tests, files or | ||
| 128 | + features that were not asked for. | ||
| 129 | + - Never run a git command unless the user says git, commit or push. | ||
| 130 | + - Never move, rename or delete a file unless the user asked for it. | ||
| 131 | + - Then answer in English, in a few lines. | ||
| 132 | + - If you don't know how to use a <cli>, run `<cli> --help` (or `<cli> help`) | ||
| 133 | + to understand the options, then run the command. | ||
| 134 | + | ||
| 135 | + BACKGROUND JOBS | ||
| 136 | + Never let a command block the answer. Anything that serves, watches or runs | ||
| 137 | + long goes to the background, with BOTH streams redirected and its pid kept: | ||
| 138 | + | ||
| 139 | + nohup <command> > /tmp/<job>.log 2>&1 & echo $! > /tmp/<job>.pid | ||
| 140 | + | ||
| 141 | + Redirecting only stdout still blocks until the process exits. Read the | ||
| 142 | + `bg-jobs` skill before you wait on, inspect or stop such a job — each has a | ||
| 143 | + rule you cannot guess. Stop every job you started before you finish, and say | ||
| 144 | + which ones you left running. | ||
| 145 | + | ||
| 146 | +# Generation settings (OpenAI API keys). Low temperature for a coding agent: | ||
| 147 | +# we want precise and reproducible answers, not creativity. | ||
| 148 | +sampling: | ||
| 149 | + temperature: 0.0 | ||
| 150 | + parallel_tool_calls: false | ||
| 151 | + top_p: 0.9 | ||
| 152 | + max_tokens: 4096 | ||
| 153 | + | ||
| 154 | +watchdogTimeout: 30s | ||
| 155 | + | ||
| 156 | +# Context compression | ||
| 157 | +context: | ||
| 158 | + enabled: false | ||
| 159 | + | ||
| 160 | + # Compress when the history reaches this share of the window. | ||
| 161 | + threshold: 75 | ||
| 162 | + | ||
| 163 | + # Fallback on the message count, for when the window is unknown. | ||
| 164 | + # One command costs 2 messages (call + response), so 80 is roughly 30 commands of history. | ||
| 165 | + maxMessages: 80 | ||
| 166 | + | ||
| 167 | + # How many recent questions (with their commands and answers) are kept as-is instead of being summarised. | ||
| 168 | + # The model works on the last few turns, so summarising them would make it redo what it just did. | ||
| 169 | + keepLastTurns: 3 | ||
| 170 | + | ||
| 171 | + # max_tokens of the summary request. | ||
| 172 | + summaryMaxTokens: 1200 | ||
| 173 | + | ||
| 174 | + # Replaces the built-in summary prompt when set. | ||
| 175 | + # Empty = built-in. | ||
| 176 | + prompt: "" | ||
| 177 | + | ||
| 178 | + showStats: true | ||
| 179 | + | ||
| 180 | +displayCommands: true | ||
| 181 | +# Show what a thinking model reasons about before it answers (dim italic in the | ||
| 182 | +# terminal, its own style in the window, a thought chunk in an editor). | ||
| 183 | +# The reasoning is always READ — it is what keeps the watchdog alive while the | ||
| 184 | +# model thinks — this only says whether to show it. | ||
| 185 | +showThinking: true | ||
| 186 | + | ||
| 187 | +# MCP (Model Context Protocol) Client Configuration | ||
| 188 | +# Enables access to remote MCP servers for additional tools | ||
| 189 | +mcp: | ||
| 190 | + enabled: true | ||
| 191 | + permissions: ask # Ask before executing MCP tools (recommended for security) | ||
| 192 | + discoveryTimeout: 10s # Timeout for discovering tools at startup | ||
| 193 | + | ||
| 194 | + servers: | ||
| 195 | + # DuckDuckGo MCP Server - Hosted by HasData (COMMENTED OUT PER USER REQUEST) | ||
| 196 | + # Provides web search capabilities. Requires HASDATA_API_KEY. | ||
| 197 | + # Free tier: 1,000 credits/month (100 searches) | ||
| 198 | + # - name: duckduckgo | ||
| 199 | + # transport: http | ||
| 200 | + # url: https://mcp.hasdata.com/mcp?apis=duckduckgo | ||
| 201 | + # timeout: 30s | ||
| 202 | + # auth: | ||
| 203 | + # type: api_key | ||
| 204 | + # apiKeyEnv: HASDATA_API_KEY | ||
| 205 | + # apiKeyHeader: x-api-key | ||
| 206 | + # # Optional: filter which tools to expose | ||
| 207 | + # # tools: | ||
| 208 | + # # - hasdata_duckduckgo_serp_getSearchResults | ||
| 209 | + | ||
| 210 | + # Context7 MCP Server - Provides documentation and code context | ||
| 211 | + # Requires authentication via API key | ||
| 212 | + - name: context7 | ||
| 213 | + transport: http | ||
| 214 | + url: https://mcp.context7.com/mcp | ||
| 215 | + timeout: 60s # Longer timeout for potentially complex queries | ||
| 216 | + auth: | ||
| 217 | + type: bearer | ||
| 218 | + tokenEnv: CONTEXT7_API_KEY # Set this environment variable with your API key | ||
| 219 | + headers: | ||
| 220 | + Accept: "application/json, text/event-stream" | ||
| 221 | + # Optional: filter which tools to expose | ||
| 222 | + # tools: | ||
| 223 | + # - resolve-library-id | ||
| 224 | + # - query-docs | ||
| 225 | + | ||
| 226 | + # Wikipedia MCP Server - Free Wikipedia access, no authentication required | ||
| 227 | + - name: wikipedia | ||
| 228 | + transport: http | ||
| 229 | + url: https://wikipedia-mcp.vercel.app/mcp | ||
| 230 | + timeout: 30s | ||
| 231 | + auth: | ||
| 232 | + type: none # No authentication required | ||
| 233 | + headers: | ||
| 234 | + Accept: "application/json, text/event-stream" | ||
| 235 | + # Optional: filter which tools to expose | ||
| 236 | + # tools: | ||
| 237 | + # - search | ||
| 238 | + # - get_article | ||
| 239 | + | ||
| 240 | + # GitHub MCP Server - Official GitHub MCP server | ||
| 241 | + # Requires GitHub Personal Access Token (PAT) with appropriate scopes | ||
| 242 | + # Get a PAT from: https://github.com/settings/tokens | ||
| 243 | + # Required scopes depend on which tools you want to use | ||
| 244 | + - name: github | ||
| 245 | + transport: http | ||
| 246 | + url: https://api.githubcopilot.com/mcp/ | ||
| 247 | + timeout: 30s | ||
| 248 | + auth: | ||
| 249 | + type: bearer | ||
| 250 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN # Set this environment variable with your GitHub PAT | ||
| 251 | + headers: | ||
| 252 | + Accept: "application/json, text/event-stream" | ||
| 253 | + # Optional: filter which tools to expose (uncomment to limit tools) | ||
| 254 | + tools: | ||
| 255 | + - get_me | ||
| 256 | + - list_repositories | ||
| 257 | + - list_issues | ||
| 258 | + - list_pull_requests | ||
| 259 | + - get_file_contents | ||
| 260 | + - search_code | ||
| 261 | + - search_repositories | ||
| 262 | + - create_pull_request | ||
| 263 | + - add_issue_comment | ||
| 264 | + # curl -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" https://api.github.com/user | ||
modified
demo/main.go +21 -29 | @@ -1,41 +1,33 @@ | ||
| 1 | 1 | package main |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | - "encoding/json" | |
| 4 | + "context" | |
| 5 | 5 | "fmt" |
| 6 | 6 | "log" |
| 7 | - "net/http" | |
| 8 | -) | |
| 9 | 7 | |
| 10 | -// Animal struct to represent an animal | |
| 11 | -type Animal struct { | |
| 12 | - Name string `json:"name"` | |
| 13 | - Species string `json:"species"` | |
| 14 | -} | |
| 8 | + "github.com/sashabaranov/go-openai" | |
| 9 | +) | |
| 15 | 10 | |
| 16 | -// Sample data | |
| 17 | -var animals = []Animal{ | |
| 18 | - {Name: "Lion", Species: "Mammal"}, | |
| 19 | - {Name: "Elephant", Species: "Mammal"}, | |
| 20 | - {Name: "Eagle", Species: "Bird"}, | |
| 21 | - {Name: "Shark", Species: "Fish"}, | |
| 22 | -} | |
| 11 | +func main() { | |
| 12 | + // Create a new OpenAI client | |
| 13 | + client := openai.NewClient("YOUR_API_KEY") | |
| 23 | 14 | |
| 24 | -// handler function to serve the animal list | |
| 25 | -func animalHandler(w http.ResponseWriter, r *http.Request) { | |
| 26 | - // Set the content type to JSON | |
| 27 | - w.Header().Set("Content-Type", "application/json") | |
| 15 | + // Create a context | |
| 16 | + ctx := context.Background() | |
| 28 | 17 | |
| 29 | - // Encode the animal slice to JSON | |
| 30 | - json.NewEncoder(w).Encode(animals) | |
| 31 | -} | |
| 18 | + // Create a request | |
| 19 | + req := openai.CompletionRequest{ | |
| 20 | + Model: openai.ModelGPT35Turbo, | |
| 21 | + Prompt: []string{"Hello, world!"}, | |
| 22 | + MaxTokens: 100, | |
| 23 | + } | |
| 32 | 24 | |
| 33 | -func main() { | |
| 34 | - // Define the handler for the root path | |
| 35 | - http.HandleFunc("/", animalHandler) | |
| 25 | + // Get a response | |
| 26 | + resp, err := client.CreateCompletion(ctx, req) | |
| 27 | + if err != nil { | |
| 28 | + log.Fatal(err) | |
| 29 | + } | |
| 36 | 30 | |
| 37 | - // Start the server on port 8080 | |
| 38 | - port := ":8080" | |
| 39 | - fmt.Printf("Starting server on http://localhost%s\n", port) | |
| 40 | - log.Fatal(http.ListenAndServe(port, nil)) | |
| 31 | + // Print the response | |
| 32 | + fmt.Println(resp.Choices[0].Text) | |
| 41 | 33 | } |
| @@ -1,41 +1,33 @@ | |||
| 1 | package main | 1 | package main |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | - "encoding/json" | 4 | + "context" |
| 5 | "fmt" | 5 | "fmt" |
| 6 | "log" | 6 | "log" |
| 7 | - "net/http" | ||
| 8 | -) | ||
| 9 | 7 | ||
| 10 | -// Animal struct to represent an animal | 8 | + "github.com/sashabaranov/go-openai" |
| 11 | -type Animal struct { | 9 | +) |
| 12 | - Name string `json:"name"` | ||
| 13 | - Species string `json:"species"` | ||
| 14 | -} | ||
| 15 | 10 | ||
| 16 | -// Sample data | 11 | +func main() { |
| 17 | -var animals = []Animal{ | 12 | + // Create a new OpenAI client |
| 18 | - {Name: "Lion", Species: "Mammal"}, | 13 | + client := openai.NewClient("YOUR_API_KEY") |
| 19 | - {Name: "Elephant", Species: "Mammal"}, | ||
| 20 | - {Name: "Eagle", Species: "Bird"}, | ||
| 21 | - {Name: "Shark", Species: "Fish"}, | ||
| 22 | -} | ||
| 23 | 14 | ||
| 24 | -// handler function to serve the animal list | 15 | + // Create a context |
| 25 | -func animalHandler(w http.ResponseWriter, r *http.Request) { | 16 | + ctx := context.Background() |
| 26 | - // Set the content type to JSON | ||
| 27 | - w.Header().Set("Content-Type", "application/json") | ||
| 28 | 17 | ||
| 29 | - // Encode the animal slice to JSON | 18 | + // Create a request |
| 30 | - json.NewEncoder(w).Encode(animals) | 19 | + req := openai.CompletionRequest{ |
| 31 | -} | 20 | + Model: openai.ModelGPT35Turbo, |
| 21 | + Prompt: []string{"Hello, world!"}, | ||
| 22 | + MaxTokens: 100, | ||
| 23 | + } | ||
| 32 | 24 | ||
| 33 | -func main() { | 25 | + // Get a response |
| 34 | - // Define the handler for the root path | 26 | + resp, err := client.CreateCompletion(ctx, req) |
| 35 | - http.HandleFunc("/", animalHandler) | 27 | + if err != nil { |
| 28 | + log.Fatal(err) | ||
| 29 | + } | ||
| 36 | 30 | ||
| 37 | - // Start the server on port 8080 | 31 | + // Print the response |
| 38 | - port := ":8080" | 32 | + fmt.Println(resp.Choices[0].Text) |
| 39 | - fmt.Printf("Starting server on http://localhost%s\n", port) | ||
| 40 | - log.Fatal(http.ListenAndServe(port, nil)) | ||
| 41 | } | 33 | } |
added
demo/test.txt +3 -0 | new file mode 100644 | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +event: message | |
| 2 | +data: {"jsonrpc":"2.0","id":1,"result":{"ttlMs":0,"cacheScope":"public","tools":[{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Add review comment to the requester's latest pending pull request review"},"description":"Add review comment to the requester's latest pending pull request review. A pending review needs to already exist to call this (check with the user if not sure).","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"The text of the review comment"},"line":{"type":"number","description":"The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"path":{"type":"string","description":"The relative path to the file that necessitates a comment"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"side":{"type":"string","description":"The side of the diff to comment on. LEFT indicates the previous state, RIGHT indicates the new state","enum":["LEFT","RIGHT"]},"startLine":{"type":"number","description":"For multi-line comments, the first line of the range that the comment applies to"},"startSide":{"type":"string","description":"For multi-line comments, the starting side of the diff that the comment applies to. LEFT indicates the previous state, RIGHT indicates the new state","enum":["LEFT","RIGHT"]},"subjectType":{"type":"string","description":"The level at which the comment is targeted","enum":["FILE","LINE"]}},"required":["owner","repo","pullNumber","path","body","subjectType"]},"name":"add_comment_to_pending_review","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Add comment to issue or pull request"},"description":"Add a comment and/or reaction to a specific issue or issue comment in a GitHub repository. Use this tool with pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add or react to review comments. At least one of body or reaction is required.","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"Comment content. Required unless reaction is provided.","minLength":1},"comment_id":{"type":"integer","description":"The numeric ID of the issue or pull request comment to react to. Use this for reactions to comments; omit it to react to the issue or pull request itself. Cannot be combined with body.","minimum":1},"issue_number":{"type":"number","description":"Issue or pull request number to comment on or react to."},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"reaction":{"type":"string","description":"Emoji reaction to add. Required unless body is provided.","enum":["+1","-1","laugh","confused","heart","hooray","rocket","eyes"]},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","issue_number"]},"name":"add_issue_comment","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Add reply to pull request comment"},"description":"Add a reply and/or reaction to an existing pull request comment. This can create a new comment linked as a reply to the specified comment, add an emoji reaction to the specified comment, or do both. At least one of body or reaction is required.","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"The text of the reply. Required unless reaction is provided."},"commentId":{"type":"number","description":"The numeric ID of the pull request review comment to reply or react to. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...).","minimum":1},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number. Required when body is provided."},"reaction":{"type":"string","description":"Emoji reaction to add. Required unless body is provided.","enum":["+1","-1","laugh","confused","heart","hooray","rocket","eyes"]},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","commentId"]},"name":"add_reply_to_pull_request_comment","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":true,"readOnlyHint":false,"title":"Assign Copilot to issue"},"description":"Assign Copilot to a specific issue in a GitHub repository.\n\nThis tool can help with the following outcomes:\n- a Pull Request created with source code changes to resolve the issue\n\n\nMore information can be found at:\n- https://docs.github.com/en/copilot/concepts/agents/cloud-agent/about-cloud-agent\n","inputSchema":{"type":"object","properties":{"base_ref":{"type":"string","description":"Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch"},"custom_instructions":{"type":"string","description":"Optional custom instructions to guide the agent beyond the issue body. Use this to provide additional context, constraints, or guidance that is not captured in the issue description"},"issue_number":{"type":"number","description":"Issue number"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","issue_number"]},"name":"assign_copilot_to_issue","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Create branch"},"description":"Create a new branch in a GitHub repository","inputSchema":{"type":"object","properties":{"branch":{"type":"string","description":"Name for new branch"},"from_branch":{"type":"string","description":"Source branch (defaults to repo default)"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","branch"]},"name":"create_branch","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Create or update file"},"description":"Create or update a single file in a GitHub repository. \nIf updating, you should provide the SHA of the file you want to update. Use this tool to create or update a file in a GitHub repository remotely; do not use it for local file operations.\n\nTo obtain the current blob SHA before updating, call the get_file_contents tool with the same owner, repo, and path, and set its ref parameter to this tool's branch value. The first text result reports the blob SHA for the requested path.\n\nSHA MUST be provided for existing file updates.\n","inputSchema":{"type":"object","properties":{"allow_symlink_write":{"type":"boolean","description":"Set true to update a symbolic link itself; content must be its new target path.","default":false},"branch":{"type":"string","description":"Branch to create/update the file in"},"content":{"type":"string","description":"Content of the file, exactly as it should appear once written. Do not base64-encode it; this server does that before calling the REST API."},"message":{"type":"string","description":"Commit message"},"owner":{"type":"string","description":"Repository owner (username or organization)","x-mcp-header":"owner"},"path":{"type":"string","description":"Path where to create/update the file"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sha":{"type":"string","description":"The blob SHA of the file being replaced. Required if the file already exists. Retrieve it with get_file_contents using the same owner, repo, and path, with ref set to this tool's branch value."}},"required":["owner","repo","path","content","message","branch"]},"name":"create_or_update_file","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Open new pull request"},"description":"Create a new pull request in a GitHub repository.","inputSchema":{"type":"object","properties":{"base":{"type":"string","description":"Branch to merge into"},"body":{"type":"string","description":"PR description"},"draft":{"type":"boolean","description":"Create as draft PR"},"head":{"type":"string","description":"Branch containing changes"},"maintainer_can_modify":{"type":"boolean","description":"Allow maintainer edits"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"reviewers":{"type":"array","items":{"type":"string"},"description":"GitHub usernames or ORG/team-slug team reviewers to request reviews from"},"title":{"type":"string","description":"PR title"}},"required":["owner","repo","title","head","base"]},"name":"create_pull_request","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Perform task with GitHub Copilot coding agent"},"description":"Delegate a task to GitHub Copilot coding agent to perform in the background. The agent will create a pull request with the implementation. You should use this tool if the user asks to create a pull request to perform a specific task, or if the user asks Copilot to do something.","inputSchema":{"type":"object","properties":{"base_ref":{"type":"string","description":"Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch"},"owner":{"type":"string","description":"Repository owner. You can guess the owner, but confirm it with the user before proceeding.","x-mcp-header":"owner"},"problem_statement":{"type":"string","description":"Detailed description of the task to be performed (e.g., 'Implement a feature that does X', 'Fix bug Y', etc.)"},"repo":{"type":"string","description":"Repository name. You can guess the repository name, but confirm it with the user before proceeding.","x-mcp-header":"repo"},"title":{"type":"string","description":"Title for the pull request that will be created"}},"required":["owner","repo","problem_statement","title"]},"name":"create_pull_request_with_copilot","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Create repository"},"description":"Create a new GitHub repository in your account or specified organization","inputSchema":{"type":"object","properties":{"autoInit":{"type":"boolean","description":"Initialize with README"},"description":{"type":"string","description":"Repository description"},"name":{"type":"string","description":"Repository name"},"organization":{"type":"string","description":"Organization to create the repository in (omit to create in your personal account)"},"private":{"type":"boolean","description":"Whether the repository should be private. Defaults to true (private) when omitted.","default":true}},"required":["name"]},"name":"create_repository","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"destructiveHint":true,"idempotentHint":false,"readOnlyHint":false,"title":"Delete file"},"description":"Delete a file from a GitHub repository","inputSchema":{"type":"object","properties":{"branch":{"type":"string","description":"Branch to delete the file from"},"message":{"type":"string","description":"Commit message"},"owner":{"type":"string","description":"Repository owner (username or organization)","x-mcp-header":"owner"},"path":{"type":"string","description":"Path to the file to delete"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","path","message","branch"]},"name":"delete_file","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Fork repository"},"description":"Fork a GitHub repository to your account or specified organization","inputSchema":{"type":"object","properties":{"organization":{"type":"string","description":"Organization to fork to"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"fork_repository","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACuElEQVRIibWTTUhUYRiFn/fOdYyoydQxk4LEGzN3RudaLYL+qRaBQYsIItoHCW37ISNbRwUFLWoRZBEt+4EIooKoTdZQ6TWaNIgouzJkuGhG731b6JTojDNBntX3ne+c97zfH8wzZCbREm9bZ4hsQvkeDvl3+/r6xuYqEIvFFgdSvRuDqCrPMu6bVyUDrITTjdI1jR8KBbrj/fs3Q8WLp5p9Qx4BzVOUInIm058+XdAY0ztH6RLhSpAza1RlI2jENzhfqntfjAugEdTYMFEtS0GvonrKslNrZwWIhDYDMh6Wo4ODvaMfB9LPFaMHZGvJ8xHdAlzPDLx+8Smd/pE39SggAptnB2gwDBD6ReJvhSCpMFyq/uSa/NFX5UMJgGCaxywMwiH/bi4wh0SCOy1x5waiCUF2gnSW3AByEfSSZTsPVXFF9CDC4ALx7xU0ocLA87x8tG7ZHRUShsheVMKInMy46culArIj317WRpd7KB2GsAl4bKoccN2330t5ALBsJ7ASTvecoun6hNNt2U5QbM0oRip8E6Wt0gCUFPC12FKoGFnX0BgBDtVGG3/W1qzqz2a/5IrpLGt9pLahvhPhCKrnsiPDT2dqZv1kgGQyGc4FZg+wr8I93F6y0DzY29s7XlHAnw7j7dswgg2oRCYZPTBluzk51VEwXmQG0k8qbGRuWHbqiWWn/qlY0Uv+n5j3gKKvaCaSyeSimrqms4hsB4kurW9c0bSs/pnneflyXrOcACCn5jWEPSr0AAgczvlVTVT+ykojFlvTZNmOWvHU8QJnJVInLNtR2163vJy/7B0EpjYAqBhugVMVF8A3goZy/rJHFGa8P4fpCXosHm9PqwbiwzHAqyLvlvPP+dEKWG23dyh6C1g0RY0Jsv+Dm77/XwIAWlpbVzJh7gLAnHjw8d27z5V65xW/AVGM6Ekx9nZCAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABoUlEQVRIibWUPS+EQRSFz0hsxaIgIZHYllDYiiiUEn6Bn0Dho6Nhf4GPjkYn8ZEgGqFRSNBoVTQKdhEROsk+mrt2svvu7Gutk7yZzJlz77nzztyR/hmulADSkkYk5SQdO+c+QwmAZkkTktolXTjnbkLiDJCniHsgFdCnTFNAHliuJE6bYANoAYaBF+AwYHBkmiGgFdi0HINR4lmrotXjVoG3gMEbsOLN2yzHTIFr8PRZG3s9rs/jo5At0fd6fFk1TfY/X4A14MyqmQrsYNo0pxbzCtwBTZUCUsAh8GHCKaDspnl6ZyZ3FnMA9AR2/BOYBzJVhUV9BshHrTVEkZKeJPXHNZA0IOkxttrrhzkgGdAlgXnTLv3GIAHsEh87QGNUrooHaEajkoYlFXYxaeO2je+SLp1z57Grr2J4DvwqWaVDrhv+3SAWrMvXgWcgZ10b3a01GuwDX8CWfV/AXr2Sd9lVXPC4ReM6q8XHOYMOG2897rZkrXZY0+WAK6DHHsRr4xJ/NjCTcXstC/gAxuPEBju5xKRb0phNT5xzD7UUW3d8A4p92DZKdSwEAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get commit details"},"description":"Get details for a commit from a GitHub repository","inputSchema":{"type":"object","properties":{"detail":{"type":"string","description":"Level of detail to include for changed files. \"none\" omits stats and files entirely. \"stats\" (default) includes per-file metadata: filename, status, and lines-of-code counts (additions, deletions, changes), with no patch content. \"full_patch\" additionally includes the unified diff content for each file and can be very large.","default":"stats","enum":["none","stats","full_patch"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sha":{"type":"string","description":"Commit SHA, branch name, or tag name"}},"required":["owner","repo","sha"]},"name":"get_commit","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"openWorldHint":false,"readOnlyHint":true,"title":"Get Copilot coding agent job status"},"description":"Get the status of a GitHub Copilot coding agent job. Use this to check if a previously submitted task has completed and to get the pull request URL once it's created. Provide the job ID (from create_pull_request_with_copilot) or pull request number (from assign_copilot_to_issue), or any pull request you want agent sessions for.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Job ID or pull request number"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","id"]},"name":"get_copilot_job_status","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get file or directory contents"},"description":"Get the contents of a file or directory from a GitHub repository","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["type","name","path","size","sha","url","git_url","html_url","download_url"]},"description":"Subset of fields to return for each entry when the path is a directory. If omitted, all fields are returned. Ignored when the path is a single file. Use this to reduce response size when listing directories and you only need specific fields, e.g. just 'name' and 'type'."},"owner":{"type":"string","description":"Repository owner (username or organization)","x-mcp-header":"owner"},"path":{"type":"string","description":"Path to file/directory","default":"/"},"ref":{"type":"string","description":"Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sha":{"type":"string","description":"Accepts optional commit SHA. If specified, it will be used instead of ref"}},"required":["owner","repo"]},"name":"get_file_contents","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get a specific label from a repository"},"description":"Get a specific label from a repository.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Label name."},"owner":{"type":"string","description":"Repository owner (username or organization name)","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","name"]},"name":"get_label","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get latest release"},"description":"Get the latest release in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"get_latest_release","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get my user profile"},"description":"Get details of the authenticated GitHub user. Use this when a request is about the user's own profile for GitHub. Or when information is missing to build other tool calls.","inputSchema":{"type":"object","properties":{}},"name":"get_me","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACSUlEQVRIidWVv09TURTHP+e+2mJMEOLiQBig4GtN4WEHRQxGMfHHYkx0YpJJR+OmjMTNP0CjRv4DJyNETXTQODRQSkmJTweYBUw0KeX1HoeCKY3v0eKi3/Gdc74/zrs3F/53SJN9Jul6owhpAJQlvzT/HrB/LZB0h4YR+wjI7B6UBVW55ZfmPkY6a4J8FugQ1euOLbc7ttyuyDWLJhA7k0wNnNpvAulLefOKdiZM4BWLxbX6Yncm0xkPTB5lzS/lhwBtKUHS9c4qOiCqdxrJAVYKhXVE7iIMJt2h0TCe8BVt/1Cjm7OhPZXETK23mm5ZQMTGQom3YW0gACoS2hsqoNbJAwTm4FjocFtwEcCozYcajTBo+lLenEUTWzE7vFIorNcXXdc9EpCYB775pfwJQu5E1BqsVW6L8CoemHwy7d39vfN4+VJgeYhwGPRGGPleCQCkN+XdE3Tqz1W97y8tPIgkCCv092dc68gzkGFgHXiNsFrLJt2IjgGdwAfH6sTy8sJy0wK9xwbPieEFSlmRyY5DzvNcLrdV35PNZg9s/KzeFNEpII6aq35p7t2eAjXn5hOwqk718pfFxdXQ/EDP8Wy3scFLoMuxerIxSeMxFeuYpyjlZsgBvhZzK9bErgAVa+RJo+ldAn0p7wJwWpHJZsjrRVRlUuFMT3rgfEQCOw5stDlb082S70CCH9PAd2NlPFRAkRGEN8VisdKqgO/7mwJvEUZCBYCjwOdWyXdga7Nd9d9232SRCSo28oWKgjjVxxrElvY7/2/iF/Bu47CZ2fOnAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABaElEQVRIidWUvUrDcBTFz79YnRSii4N2cVB8AuvgByKuQqmrILQPoe9SfABxV5wUna2IVLq1k11adffn4A3UNPknjQ56IARyzz3ncj8i/Xe4LCSgIGlD0qp9epJ07Zz7+HEFQBl4YBRNoPwb4u9AB6gA0/bsAy3gDVjLK+6syg4wGxMPLHYPZGp1VGDLWlHxcKrG2UziFDwe4UAvPZyLCHcsgwlPLETYmkSuz6Bp7x0PZy/CzQ6gYENuAUFMfA7o2pB9hXpN1m0VOzbQGXsOTDz/mpqBA05ijizEcZpG4v4CK5IaksqS+pKuJHUtXNLXbAJJd5KOnHPP41S+DbwCL0ANKMZwikAd6AED3y2MVG7ij8BiBn7JuANgOY3sgFurPFU8YtIDbry/DWDXhlfLKj6UW7fc5LsBToE+MJnDYMra1PCR2sDZuOJD+efAt22KXuC8pHZeA8td8FVQBZIJKQCWgMO8+X8Tn12zhtgfmPjeAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get a release by tag name"},"description":"Get a specific release by its tag name in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"tag":{"type":"string","description":"Tag name (e.g., 'v1.0.0')"}},"required":["owner","repo","tag"]},"name":"get_release_by_tag","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get tag details"},"description":"Get details about a specific git tag in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"tag":{"type":"string","description":"Tag name"}},"required":["owner","repo","tag"]},"name":"get_tag","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get team members"},"description":"Get member usernames of a specific team in an organization. Limited to organizations accessible with current credentials","inputSchema":{"type":"object","properties":{"org":{"type":"string","description":"Organization login (owner) that contains the team."},"team_slug":{"type":"string","description":"Team slug"}},"required":["org","team_slug"]},"name":"get_team_members","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACSUlEQVRIidWVv09TURTHP+e+2mJMEOLiQBig4GtN4WEHRQxGMfHHYkx0YpJJR+OmjMTNP0CjRv4DJyNETXTQODRQSkmJTweYBUw0KeX1HoeCKY3v0eKi3/Gdc74/zrs3F/53SJN9Jul6owhpAJQlvzT/HrB/LZB0h4YR+wjI7B6UBVW55ZfmPkY6a4J8FugQ1euOLbc7ttyuyDWLJhA7k0wNnNpvAulLefOKdiZM4BWLxbX6Yncm0xkPTB5lzS/lhwBtKUHS9c4qOiCqdxrJAVYKhXVE7iIMJt2h0TCe8BVt/1Cjm7OhPZXETK23mm5ZQMTGQom3YW0gACoS2hsqoNbJAwTm4FjocFtwEcCozYcajTBo+lLenEUTWzE7vFIorNcXXdc9EpCYB775pfwJQu5E1BqsVW6L8CoemHwy7d39vfN4+VJgeYhwGPRGGPleCQCkN+XdE3Tqz1W97y8tPIgkCCv092dc68gzkGFgHXiNsFrLJt2IjgGdwAfH6sTy8sJy0wK9xwbPieEFSlmRyY5DzvNcLrdV35PNZg9s/KzeFNEpII6aq35p7t2eAjXn5hOwqk718pfFxdXQ/EDP8Wy3scFLoMuxerIxSeMxFeuYpyjlZsgBvhZzK9bErgAVa+RJo+ldAn0p7wJwWpHJZsjrRVRlUuFMT3rgfEQCOw5stDlb082S70CCH9PAd2NlPFRAkRGEN8VisdKqgO/7mwJvEUZCBYCjwOdWyXdga7Nd9d9232SRCSo28oWKgjjVxxrElvY7/2/iF/Bu47CZ2fOnAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABaElEQVRIidWUvUrDcBTFz79YnRSii4N2cVB8AuvgByKuQqmrILQPoe9SfABxV5wUna2IVLq1k11adffn4A3UNPknjQ56IARyzz3ncj8i/Xe4LCSgIGlD0qp9epJ07Zz7+HEFQBl4YBRNoPwb4u9AB6gA0/bsAy3gDVjLK+6syg4wGxMPLHYPZGp1VGDLWlHxcKrG2UziFDwe4UAvPZyLCHcsgwlPLETYmkSuz6Bp7x0PZy/CzQ6gYENuAUFMfA7o2pB9hXpN1m0VOzbQGXsOTDz/mpqBA05ijizEcZpG4v4CK5IaksqS+pKuJHUtXNLXbAJJd5KOnHPP41S+DbwCL0ANKMZwikAd6AED3y2MVG7ij8BiBn7JuANgOY3sgFurPFU8YtIDbry/DWDXhlfLKj6UW7fc5LsBToE+MJnDYMra1PCR2sDZuOJD+efAt22KXuC8pHZeA8td8FVQBZIJKQCWgMO8+X8Tn12zhtgfmPjeAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get teams"},"description":"Get details of the teams the user is a member of. Limited to organizations accessible with current credentials","inputSchema":{"type":"object","properties":{"user":{"type":"string","description":"Username to get teams for. If not provided, uses the authenticated user."}}},"name":"get_teams","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACSUlEQVRIidWVv09TURTHP+e+2mJMEOLiQBig4GtN4WEHRQxGMfHHYkx0YpJJR+OmjMTNP0CjRv4DJyNETXTQODRQSkmJTweYBUw0KeX1HoeCKY3v0eKi3/Gdc74/zrs3F/53SJN9Jul6owhpAJQlvzT/HrB/LZB0h4YR+wjI7B6UBVW55ZfmPkY6a4J8FugQ1euOLbc7ttyuyDWLJhA7k0wNnNpvAulLefOKdiZM4BWLxbX6Yncm0xkPTB5lzS/lhwBtKUHS9c4qOiCqdxrJAVYKhXVE7iIMJt2h0TCe8BVt/1Cjm7OhPZXETK23mm5ZQMTGQom3YW0gACoS2hsqoNbJAwTm4FjocFtwEcCozYcajTBo+lLenEUTWzE7vFIorNcXXdc9EpCYB775pfwJQu5E1BqsVW6L8CoemHwy7d39vfN4+VJgeYhwGPRGGPleCQCkN+XdE3Tqz1W97y8tPIgkCCv092dc68gzkGFgHXiNsFrLJt2IjgGdwAfH6sTy8sJy0wK9xwbPieEFSlmRyY5DzvNcLrdV35PNZg9s/KzeFNEpII6aq35p7t2eAjXn5hOwqk718pfFxdXQ/EDP8Wy3scFLoMuxerIxSeMxFeuYpyjlZsgBvhZzK9bErgAVa+RJo+ldAn0p7wJwWpHJZsjrRVRlUuFMT3rgfEQCOw5stDlb082S70CCH9PAd2NlPFRAkRGEN8VisdKqgO/7mwJvEUZCBYCjwOdWyXdga7Nd9d9232SRCSo28oWKgjjVxxrElvY7/2/iF/Bu47CZ2fOnAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABaElEQVRIidWUvUrDcBTFz79YnRSii4N2cVB8AuvgByKuQqmrILQPoe9SfABxV5wUna2IVLq1k11adffn4A3UNPknjQ56IARyzz3ncj8i/Xe4LCSgIGlD0qp9epJ07Zz7+HEFQBl4YBRNoPwb4u9AB6gA0/bsAy3gDVjLK+6syg4wGxMPLHYPZGp1VGDLWlHxcKrG2UziFDwe4UAvPZyLCHcsgwlPLETYmkSuz6Bp7x0PZy/CzQ6gYENuAUFMfA7o2pB9hXpN1m0VOzbQGXsOTDz/mpqBA05ijizEcZpG4v4CK5IaksqS+pKuJHUtXNLXbAJJd5KOnHPP41S+DbwCL0ANKMZwikAd6AED3y2MVG7ij8BiBn7JuANgOY3sgFurPFU8YtIDbry/DWDXhlfLKj6UW7fc5LsBToE+MJnDYMra1PCR2sDZuOJD+efAt22KXuC8pHZeA8td8FVQBZIJKQCWgMO8+X8Tn12zhtgfmPjeAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get issue details"},"description":"Get information about a specific issue in a GitHub repository.","inputSchema":{"type":"object","properties":{"issue_number":{"type":"number","description":"The number of the issue"},"method":{"type":"string","description":"The read operation to perform on a single issue.\nOptions are:\n1. get - Get issue details. Also returns best-effort hierarchy flags (`has_parent`, `has_children`); `parent` and `sub_issues_summary` are optional relationship summaries, and `closed_by_pull_requests` summarizes the pull requests configured to close the issue as `total_count` plus up to 5 `references`.\n2. get_comments - Get issue comments.\n3. get_sub_issues - Get sub-issues (children) of the issue.\n4. get_parent - Get the parent issue, if this issue is a sub-issue of another.\n5. get_labels - Get labels assigned to the issue.\n","enum":["get","get_comments","get_sub_issues","get_parent","get_labels"]},"owner":{"type":"string","description":"The owner of the repository","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"The name of the repository","x-mcp-header":"repo"}},"required":["method","owner","repo","issue_number"]},"name":"issue_read","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Create or update issue/pull request"},"description":"Create a new or update an existing issue in a GitHub repository.","inputSchema":{"type":"object","properties":{"assignees":{"type":"array","items":{"type":"string"},"description":"Usernames to assign to this issue"},"body":{"type":"string","description":"Issue body content"},"duplicate_of":{"type":"number","description":"Issue number that this issue is a duplicate of. Required when state_reason is 'duplicate'."},"issue_fields":{"type":"array","items":{"type":"object","properties":{"delete":{"type":"boolean","description":"Set to true to clear this field's current value on the issue. When false or omitted, this property is ignored. Cannot be true when 'value' or 'field_option_name' is provided."},"field_name":{"type":"string","description":"Issue field name (case-insensitive). Must match a field returned by list_issue_fields for this repository or its organization."},"field_option_name":{"type":"string","description":"Option name for single-select fields. Validated against the field's options before the API call. Cannot be combined with 'value' or 'delete: true'."},"value":{"type":["string","number","boolean"],"description":"Value to set. Use for text, number, and date fields (date as YYYY-MM-DD). For single-select fields, prefer 'field_option_name' so the option is validated before the API call. Cannot be combined with 'field_option_name' or 'delete: true'."}},"required":["field_name"],"additionalProperties":false},"description":"Issue field values to set or clear. Each item requires 'field_name' and exactly one of 'value', 'field_option_name', or 'delete: true'."},"issue_number":{"type":"number","description":"Issue number to update"},"labels":{"type":"array","items":{"type":"string"},"description":"Labels to apply to this issue"},"method":{"type":"string","description":"Write operation to perform on a single issue.\nOptions are:\n- 'create' - creates a new issue.\n- 'update' - updates an existing issue.\n","enum":["create","update"]},"milestone":{"type":"number","description":"Milestone number"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"parent_issue_number":{"type":"number","description":"Issue number of the parent issue. Only used when method is 'create' and cannot be combined with issue_fields. The new issue is created and attached to this parent in the same operation.","minimum":1},"parent_owner":{"type":"string","description":"Repository owner of the parent issue. Must be provided with parent_repo. Omit both to use owner and repo. Only used when method is 'create' and parent_issue_number is provided."},"parent_repo":{"type":"string","description":"Repository name of the parent issue. Must be provided with parent_owner. Omit both to use owner and repo. Only used when method is 'create' and parent_issue_number is provided."},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"state":{"type":"string","description":"New state","enum":["open","closed"]},"state_reason":{"type":"string","description":"Reason for the state change. Ignored unless state is changed.","enum":["completed","not_planned","duplicate"]},"title":{"type":"string","description":"Issue title"},"type":{"description":"Type of this issue. For updates, pass null to remove the current type. Only use if issue types are enabled for this repository. Use list_issue_types to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter.","anyOf":[{"type":"string","minLength":1},{"type":"null"}]}},"required":["method","owner","repo"]},"name":"issue_write","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List branches"},"description":"List branches in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"list_branches","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List commits"},"description":"Get list of commits of a branch in a GitHub repository. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).","inputSchema":{"type":"object","properties":{"author":{"type":"string","description":"Author username or email address to filter commits by"},"fields":{"type":"array","items":{"type":"string","enum":["sha","html_url","commit","author","committer"]},"description":"Subset of fields to return for each commit. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields, e.g. just 'sha' and 'html_url'."},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"path":{"type":"string","description":"Only commits containing this file path will be returned"},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sha":{"type":"string","description":"Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA."},"since":{"type":"string","description":"Only commits after this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)"},"until":{"type":"string","description":"Only commits before this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)"}},"required":["owner","repo"]},"name":"list_commits","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List issue fields"},"description":"List issue fields for a repository or organization. Returns field definitions including name, type (text, number, date, single_select), and for single_select fields the list of valid option names. When repo is omitted, returns org-level fields directly.","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"The account owner of the repository or organization. The name is not case sensitive.","x-mcp-header":"owner"},"repo":{"type":"string","description":"The name of the repository. When provided, returns fields for this specific repository (inherited from its organization). When omitted, returns org-level fields directly.","x-mcp-header":"repo"}},"required":["owner"]},"name":"list_issue_fields","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List available issue types"},"description":"List supported issue types for a repository or its owner organization. When repo is omitted, returns org-level issue types directly.","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"The account owner of the repository or organization.","x-mcp-header":"owner"},"repo":{"type":"string","description":"The name of the repository. When provided, returns issue types for this specific repository. When omitted, returns org-level issue types directly.","x-mcp-header":"repo"}},"required":["owner"]},"name":"list_issue_types","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List issues"},"description":"List issues in a GitHub repository. For pagination, use the 'endCursor' from the previous response's 'pageInfo' in the 'after' parameter.","inputSchema":{"type":"object","properties":{"after":{"type":"string","description":"Cursor for pagination. Use the cursor from the previous response."},"direction":{"type":"string","description":"Order direction. If provided, the 'orderBy' also needs to be provided.","enum":["ASC","DESC"]},"field_filters":{"type":"array","items":{"type":"object","properties":{"field_name":{"type":"string","description":"Name of the custom field (e.g. \"Priority\"). Case-insensitive."},"value":{"type":"string","description":"Value to filter on. For single-select fields, the option name (e.g. \"P1\"). For dates, YYYY-MM-DD. For numbers, the numeric value as a string. For text, the text value."}},"required":["field_name","value"]},"description":"Filter by custom issue field values. Each entry takes a field_name and a value; the server looks up the field and coerces the value to its type (single-select option name, text, number, or YYYY-MM-DD date)."},"fields":{"type":"array","items":{"type":"string","enum":["number","title","body","state","user","labels","assignees","comments","created_at","updated_at","field_values"]},"description":"Subset of fields to return for each issue. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body' and 'field_values' in particular drops the largest per-result data."},"labels":{"type":"array","items":{"type":"string"},"description":"Filter by labels"},"orderBy":{"type":"string","description":"Order issues by field. If provided, the 'direction' also needs to be provided.","enum":["CREATED_AT","UPDATED_AT","COMMENTS"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"since":{"type":"string","description":"Filter by date (ISO 8601 timestamp)"},"state":{"type":"string","description":"Filter by state, by default both open and closed issues are returned when not provided","enum":["OPEN","CLOSED"]}},"required":["owner","repo"]},"name":"list_issues","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List pull requests"},"description":"List pull requests in a GitHub repository. If the user specifies an author, then DO NOT use this tool and use the search_pull_requests tool instead.","inputSchema":{"type":"object","properties":{"base":{"type":"string","description":"Filter by base branch"},"direction":{"type":"string","description":"Sort direction","enum":["asc","desc"]},"fields":{"type":"array","items":{"type":"string","enum":["number","title","body","state","draft","merged","mergeable_state","html_url","user","labels","assignees","requested_reviewers","merged_by","head","base","additions","deletions","changed_files","commits","comments","created_at","updated_at","closed_at","merged_at","milestone"]},"description":"Subset of fields to return for each pull request. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body' in particular drops the largest per-result data."},"head":{"type":"string","description":"Filter by head user/org and branch"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sort":{"type":"string","description":"Sort by","enum":["created","updated","popularity","long-running"]},"state":{"type":"string","description":"Filter by state","enum":["open","closed","all"]}},"required":["owner","repo"]},"name":"list_pull_requests","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List releases"},"description":"List releases in a GitHub repository","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["id","tag_name","name","body","html_url","published_at","prerelease","draft","author"]},"description":"Subset of fields to return for each release. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body' in particular drops the largest per-release data."},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"list_releases","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List repository collaborators"},"description":"List collaborators of a GitHub repository. Results are paginated; the response includes `nextPage`, `prevPage`, `firstPage`, and `lastPage` fields. To get the next page, use the `nextPage` value as the `page` parameter.","inputSchema":{"type":"object","properties":{"affiliation":{"type":"string","description":"Filter by affiliation. Can be one of: 'outside' (outside collaborators), 'direct' (all with permissions regardless of org membership), 'all' (all collaborators). Default: 'all'","enum":["outside","direct","all"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (default 1, min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (default 30, min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"list_repository_collaborators","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List tags"},"description":"List git tags in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"list_tags","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Merge pull request"},"description":"Merge a pull request in a GitHub repository.","inputSchema":{"type":"object","properties":{"commit_message":{"type":"string","description":"Extra detail for merge commit"},"commit_title":{"type":"string","description":"Title for merge commit"},"expectedHeadSha":{"type":"string","description":"The expected SHA of the pull request's HEAD ref"},"merge_method":{"type":"string","description":"Merge method","enum":["merge","squash","rebase"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","pullNumber"]},"name":"merge_pull_request","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACeElEQVRIibWVTUhUYRSGn/e74+iiQih1F9Vcmj9sptylUVBYkO4jcNeuJBdFKxe1CYQokGrRKjCEdtmqwEVmtqomQWeiUdc2EBUtUufe0yLHn1KLGXtX5zvn4zz3vd8f/Gfp90Qs0drmpA6MT1EveDo1NfV92wB+KnMdo39Nfs4L7eSHD5Nz1QJcJYglWtsw+iUehAuRRjO1g+0KHLerbb4OIHnHAC1FdW129s3XmUJuwnBDoOPbA7BwHsD7QWq1HKYN5msBRCpB1AueLoSROSkciSUyj5ClhE6BLtYC8CpBqVRabNrdMmIiJdQjuUbQ1WI+d78WwIbykxnzU9np7ejlNq2YxQ4ebNtTKyCyWcEgYl55EDj/a7ihFEtkLkr0As2YxjwL+9aem00dCEYNzvnJzLDvH27aaM5y80HEnKGHKGwPnEbT6fSOvzpAmrDQnkncpC7siiUzz2QqIPu25iOuGBorTufO/AJmH0v2ajHwuoHhrQHATOH9rQPJ7IjDLgs6kZ0F6it1AzArVcZLdUE+WnYgmv/uYFmz+dxH4NJGNT+RfYLCE7F4tn0pGkxHy94AmBm8/GfAVvIs7AukUTkbj5YdYIbZ9WJh8m1lzrrbNB4/tD+QuyPsdCibF26gmM/dY/NdRDqd3rEYeN04mswYL+ZXm68DxOPxnWXXMClsp+GGhCWBTtClYj53t1qXK78oVH2XYB/mHZ0pvHsN4Cczzw3rBaoGrJ6D5ZUvN1i+kjI0LWiptjmscbC88hZZCAf2trZeq1v0UsJ6wF7UAlhxUMxPvkW6AboQLbvPcjaO+BIx11cL4I9H308eOiLRQUhpOx79/66fNKzrOCYNDm0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABjElEQVRIibWVPS/DURTGnysSC0HiZdWVrZ28JDaLT8BHaBsMdjqZJDXiAzC2LF5mX6GtATGiIsGARH+Gnj9X8a/kf3uWe3Py3Oc559xz75E6bK7VAWQkzUi6lXTonHsOpgYUgAZfdgmkQpFnjHwb6AemgDpQCiWwYlEPeL4i8JCEt8vb39g67vkmPH8yA3qt5nVgCzi1jLJBBEwkBZSAdxPKAj86LYQQQCU4cYvAKzDUSYF3YC+uRIAD8sA58ACU//VuTODE1n1g+A9c3jBH1tJ1a5TeCPNrdACSCpKeJG1IepN0LKkm6dGDrkqqOOdm7dyUpDNJi865PUnqjsvEObcJHEhaljQnaV5STwvszttXbR2J441KtB4LauLKVpZpYBDYte8mHUogZTWPrAGstTtQBl6AayDX7qHZD7AALMVGDvQBV5ZyETi2qHLtMvmXWRQAk57vBKgl4fV/0+jmq56vImk0icCnAWm7pB3riGngnlADx0TW+T4yL4CxJJy/Df20mkP/TqGHfifsA7INs3X5i3+yAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get details for a single pull request"},"description":"Get information on a specific pull request in GitHub repository.","inputSchema":{"type":"object","properties":{"after":{"type":"string","description":"Cursor for pagination, used only by the get_review_comments method. Pass the endCursor from the previous page's PageInfo to fetch the next page."},"method":{"type":"string","description":"Action to specify what pull request data needs to be retrieved from GitHub. \nPossible options: \n 1. get - Get details of a specific pull request.\n 2. get_diff - Get the diff of a pull request.\n 3. get_status - Get combined commit status of a head commit in a pull request.\n 4. get_files - Get the list of files changed in a pull request. Use with pagination parameters to control the number of results returned.\n 5. get_commits - Get the list of commits on a pull request. Use with pagination parameters to control the number of results returned.\n 6. get_review_comments - Get review threads on a pull request. Each thread contains logically grouped review comments made on the same code location during pull request reviews. Returns thread metadata and comments with nullable current and original line-range coordinates (line, start_line, original_line, original_start_line). Current coordinates are omitted when unavailable, such as for outdated comments. Use cursor-based pagination (perPage, after) to control results.\n 7. get_reviews - Get the reviews on a pull request. When asked for review comments, use get_review_comments method. Use with pagination parameters to control the number of results returned.\n 8. get_comments - Get comments on a pull request. Use this if user doesn't specifically want review comments. Use with pagination parameters to control the number of results returned.\n 9. get_check_runs - Get check runs for the head commit of a pull request. Check runs are the individual CI/CD jobs and checks that run on the PR.\n","enum":["get","get_diff","get_status","get_files","get_commits","get_review_comments","get_reviews","get_comments","get_check_runs"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["method","owner","repo","pullNumber"]},"name":"pull_request_read","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Write operations (create, submit, delete) on pull request reviews"},"description":"Create and/or submit, delete review of a pull request.\n\nAvailable methods:\n- create: Create a new review of a pull request. If \"event\" parameter is provided, the review is submitted. If \"event\" is omitted, a pending review is created.\n- submit_pending: Submit an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request. The \"body\" and \"event\" parameters are used when submitting the review.\n- delete_pending: Delete an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request.\n- resolve_thread: Resolve a review thread. Requires only \"threadId\" parameter with the thread's node ID (e.g., PRRT_kwDOxxx). The owner, repo, and pullNumber parameters are not used for this method. Resolving an already-resolved thread is a no-op.\n- unresolve_thread: Unresolve a previously resolved review thread. Requires only \"threadId\" parameter. The owner, repo, and pullNumber parameters are not used for this method. Unresolving an already-unresolved thread is a no-op.\n","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"Review comment text"},"commitID":{"type":"string","description":"SHA of commit to review"},"event":{"type":"string","description":"Review action to perform.","enum":["APPROVE","REQUEST_CHANGES","COMMENT"]},"method":{"type":"string","description":"The write operation to perform on pull request review.","enum":["create","submit_pending","delete_pending","resolve_thread","unresolve_thread"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"threadId":{"type":"string","description":"The node ID of the review thread (e.g., PRRT_kwDOxxx). Required for resolve_thread and unresolve_thread methods. Get thread IDs from pull_request_read with method get_review_comments."}},"required":["method","owner","repo","pullNumber"]},"name":"pull_request_review_write","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Push files to repository"},"description":"Push multiple files to a GitHub repository in a single commit","inputSchema":{"type":"object","properties":{"branch":{"type":"string","description":"Branch to push to"},"files":{"type":"array","items":{"type":"object","properties":{"content":{"type":"string","description":"file content"},"path":{"type":"string","description":"path to the file"}},"required":["path","content"],"additionalProperties":false},"description":"Array of file objects to push, each object with path (string) and content (string)"},"message":{"type":"string","description":"Commit message"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","branch","files","message"]},"name":"push_files","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Request Copilot review"},"description":"Request a GitHub Copilot code review for a pull request. Use this for automated feedback on pull requests, usually before requesting a human reviewer.","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","pullNumber"]},"name":"request_copilot_review","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"openWorldHint":false,"readOnlyHint":true,"title":"Run Secret Scanning"},"description":"Scan files, content, or recent changes for secrets such as API keys, passwords, tokens, and credentials.\n\nThis tool is intended for targeted scans of specific files, snippets, or diffs provided directly as content. The files parameter accepts either a single string or an array of strings containing raw file contents or diff hunks, and returns detected secrets with their locations and related secret scanning metadata. Content must not be empty. For full repository scanning, other mechanisms are available.\n\nCaveats:\n\n- Only files within the codebase should be scanned. Files outside of the codebase should not be sent.\n- Files listed in .gitignore should be skipped.","inputSchema":{"type":"object","properties":{"files":{"description":"A single string or an array of strings containing file contents, snippets, or diff hunks to scan for secrets. These must be raw contents, not repository file paths.","anyOf":[{"type":"string","minLength":1},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100}]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["files","owner","repo"]},"name":"run_secret_scanning","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search code"},"description":"Fast and precise code search across ALL GitHub repositories using GitHub's native search engine. Best for finding exact symbols, functions, classes, or specific code patterns.","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["name","path","sha","repository","text_matches"]},"description":"Subset of fields to return for each code search result. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'repository' and 'text_matches' in particular drops the largest per-result data."},"order":{"type":"string","description":"Sort order for results","enum":["asc","desc"]},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"Search query (GitHub code search REST). Implicit AND between terms; supports `OR`, `NOT`, and `\"quoted phrase\"` for exact match. Qualifiers: `repo:owner/repo`, `org:`, `user:`, `language:`, `path:dir` (prefix match), `filename:exact.ext`, `extension:`, `in:file`, `in:path`, `size:`, `is:archived`, `is:fork`. Max 256 chars. Examples: `WithContext language:go org:github`; `\"package main\" repo:o/r`; `func extension:go path:cmd repo:o/r`; `NOT TODO language:go repo:o/r`."},"sort":{"type":"string","description":"Sort field ('indexed' only)"}},"required":["query"]},"name":"search_code","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search commits"},"description":"Search for commits across GitHub repositories using GitHub's commit search syntax. Useful for finding specific changes, authors, or messages across one or many repositories. Searches the default branch only.","inputSchema":{"type":"object","properties":{"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"Commit search query (GitHub commit search REST). Searches commit messages on the default branch only. Scope the search with `repo:owner/repo`, `org:`, or `user:` (queries without a scope qualifier match across all of GitHub and are usually not what you want). Other qualifiers: `author:`, `committer:`, `author-name:`, `committer-name:`, `author-email:`, `committer-email:`, `author-date:`, `committer-date:` (supports `\u003e`, `\u003c`, `\u003e=`, `\u003c=`, and `YYYY-MM-DD..YYYY-MM-DD` ranges), `merge:true|false`, `hash:`, `tree:`, `parent:`, `is:public`. Examples: `repo:owner/repo fix panic`; `org:github author:defunkt committer-date:\u003e=2024-01-01`; `\"refactor cache\" repo:o/r`; `hash:abc1234 repo:o/r`."},"sort":{"type":"string","description":"Sort by author or committer date (defaults to best match)","enum":["author-date","committer-date"]}},"required":["query"]},"name":"search_commits","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search issues"},"description":"Search issues using natural-language semantic matching. Best for conceptual or paraphrased queries (e.g. \"login fails after password reset\"). Already scoped to is:issue.","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["number","title","body","state","state_reason","draft","locked","html_url","user","author_association","labels","assignee","assignees","milestone","comments","reactions","created_at","updated_at","closed_at","closed_by","type","repository_url","pull_request","field_values"]},"description":"Subset of fields to return for each issue result. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body', 'reactions', and 'labels' in particular drops the largest per-result data."},"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"owner":{"type":"string","description":"Optional repository owner. If provided with repo, only issues for this repository are listed.","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"The search query, as natural language. When the user gives alternative wordings, include them as plain words rather than joining them with OR."},"repo":{"type":"string","description":"Optional repository name. If provided with owner, only issues for this repository are listed.","x-mcp-header":"repo"},"sort":{"type":"string","description":"Sort field by number of matches of categories, defaults to best match","enum":["comments","reactions","reactions-+1","reactions--1","reactions-smile","reactions-thinking_face","reactions-heart","reactions-tada","interactions","created","updated"]}},"required":["query"]},"name":"search_issues","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search pull requests"},"description":"Search for pull requests in GitHub repositories using issues search syntax already scoped to is:pr","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["number","title","body","state","state_reason","draft","locked","html_url","user","author_association","labels","assignee","assignees","milestone","comments","reactions","created_at","updated_at","closed_at","closed_by","pull_request","repository_url"]},"description":"Subset of fields to return for each pull request result. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body', 'reactions', and 'labels' in particular drops the largest per-result data."},"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"owner":{"type":"string","description":"Optional repository owner. If provided with repo, only pull requests for this repository are listed.","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"Search query using GitHub pull request search syntax"},"repo":{"type":"string","description":"Optional repository name. If provided with owner, only pull requests for this repository are listed.","x-mcp-header":"repo"},"sort":{"type":"string","description":"Sort field by number of matches of categories, defaults to best match","enum":["comments","reactions","reactions-+1","reactions--1","reactions-smile","reactions-thinking_face","reactions-heart","reactions-tada","interactions","created","updated"]}},"required":["query"]},"name":"search_pull_requests","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search repositories"},"description":"Find GitHub repositories by name, description, readme, topics, or other metadata. Perfect for discovering projects, finding examples, or locating specific repositories across GitHub.","inputSchema":{"type":"object","properties":{"minimal_output":{"type":"boolean","description":"Return minimal repository information (default: true). When false, returns full GitHub API repository objects.","default":true},"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"Repository search query. Examples: 'machine learning in:name stars:\u003e1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering."},"sort":{"type":"string","description":"Sort repositories by field, defaults to best match","enum":["stars","forks","help-wanted-issues","updated"]}},"required":["query"]},"name":"search_repositories","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search users"},"description":"Find GitHub users by username, real name, or other profile information. Useful for locating developers, contributors, or team members.","inputSchema":{"type":"object","properties":{"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"User search query. Examples: 'john smith', 'location:seattle', 'followers:\u003e100'. Search is automatically scoped to type:user."},"sort":{"type":"string","description":"Sort users by number of followers or repositories, or when the person joined GitHub.","enum":["followers","repositories","joined"]}},"required":["query"]},"name":"search_users","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAADWklEQVRIidWTT2hcVRTGf+e+mUn9Q3WMqbbWgJ1JnMw4meiAUkoxIRG1IKK4ELqoVkQXUsVuXbropoq4EWykuBDURV2I0r9GrC0VopnXTGaefYkhDahBOg4SnSTv3eMiTphMJ6Spq57dPd+933e/794DN3rJWkA+n49W54MB4AE1aoyawqVS1xn4PPzfAsnUg48iOgyaaIJ8I7r/5wn3u2sVMM2N7nTvbsQeB42J6nOOrW12bG2zIs8ohFblZHe6d/d1Oejv74/MzlU8lGibCfqKxeKVRrwzm43HAlMAatvviqdHRkaC9QScxkVk021DwAFRfalcGv+xeXN1bq52x5ats8Crf80vnL3yx29TiZ7e59s7tg7Ft3TEc5n09PT0tG08syoiEckBGF04seaVFtuOA1ixuf9OvQP6rqj5avb3SmlHKptdU0BhaT3L1gbLsVpRgMlSYdvNUb1VlKeAqBFzsjObjbcWMNYFCMxNg2sJmE3B4wCOmEK957ru/KVy4UtxnCeBO2OheaOONX9Tk+zJlRXsUsTunLl4sdIIplKp9oC2MYR//FJ3T6uZSKZzZ7Es+OXCIECkOQFVeUFET8UCU0im+w7WMydWeyKwHEboUCuDaw2cKGpFVx76qjmYLI+dRzkE3IvqZ0RrVaK1KsqnCNsROTTpjX3fijyReSip8LCo+aFlRN3d2ZR15COQnUAFOIVwedmbdCI6CMSBc47V/Z7neivkqWxexHwC2h4lmi2VRn9dJZC4Pzcghi9Qaoq8dfstztHR0dFVvyqfz0f/nA9fFNG3gRhqnvbLP30LkOzJfQP0Kzw7WSocW+Vg+ebmAjCjTrhncnz8cqsI6rUjk+80NvgauMex+ojnuV4ynTuMcgD4G/RNv+QO199ArGOGUWrXQg4wVRydCTF7gCVr5Agg/kThIE6YQjkH8mEi1bcPQLp6+h5T9AToy37JPbIeeWN1pXOvqPKBFR2amnBPA2QymdiidY4pMmhCEgbsXqDSZsKPN0IOoIvzR4GqsbK33isWi4vG8hogoaOvG0V2oXK6WCwublTA9/0FgTMIuxr7nuf+AowIDBjgboz6GyVfcSF4wLarAGEcuC8iyj4JuXC9Ak5o3g8j4fnmvpXIe44NWg7kjVX/Ap7dYx0LcmfJAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAB20lEQVRIidXUvUuVcRQH8PPc1JYUrKWUDCmwoKa2QsTKoKEwxWhr6G1tqDVqaAn8F6I5oS0ipBp6WZqiAi1bEgIrIpcorD4NHvFye+7Vrkv94Dc853zP93venl/E/36Keg60RsRgROyOiEpEPI+IB0VR/FyzKgYw48/zBv1rJe/HN7zDKNrzDmMqfc2JoAVvk3xjib8zfa/R0ozA4WzFaAPMWGKG8vskLuDIiqK4lMHtDTAdibmY3+9rZrSnGl+piV9YqcpY3jwREUVRdEXEhog4GhGtETGJznrZHchMhhtUcCIxh0p8u/ADV+sFV3KAU2VZYBNmE7OuDsdj3K+XYGAfvua2jGXPOzLz2VzT/Q3iH2GykUCByyU/2dK50iB2B77jWj3ATjxNos+4hfG8E2mDJ+irid2LaXzCljLyQcxjDmctvkW1mFacwwd8wUCV72GKH6+X+TxeYGvd/i3je/AqRfrSNo6F5DldDS6y5LnVkFfFbcPHHGqRtu24i184tQQcytLOrJa8SuR8xh6ssrXhTm5bd+BmDq+tCYH12aYbNfbe3KbrYfH9mPhb8iqy25gusd/Ds0pEbI6ImWYFImI6IrpK7C8jojcwgu5m2dGFYyX2How0y/vvnN8dpHfeBcHNQgAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Change sub-issue"},"description":"Add a sub-issue to a parent issue in a GitHub repository.","inputSchema":{"type":"object","properties":{"after_id":{"type":"number","description":"The ID of the sub-issue to be prioritized after (either after_id OR before_id should be specified)"},"before_id":{"type":"number","description":"The ID of the sub-issue to be prioritized before (either after_id OR before_id should be specified)"},"issue_number":{"type":"number","description":"The number of the parent issue"},"method":{"type":"string","description":"The action to perform on a single sub-issue\nOptions are:\n- 'add' - add a sub-issue to a parent issue in a GitHub repository.\n- 'remove' - remove a sub-issue from a parent issue in a GitHub repository.\n- 'reprioritize' - change the order of sub-issues within a parent issue in a GitHub repository. Use either 'after_id' or 'before_id' to specify the new position.\nWrites issue hierarchy. To move a sub-issue to a new parent, use `add` with `replace_parent=true`; there is no writable parent field.\n"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"replace_parent":{"type":"boolean","description":"When true, replaces the sub-issue's current parent issue. Use with 'add' method only."},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sub_issue_id":{"type":"number","description":"The ID of the sub-issue to add. ID is not the same as issue number"}},"required":["method","owner","repo","issue_number","sub_issue_id"]},"name":"sub_issue_write","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Update issue comment"},"description":"Update the body of an existing issue or pull request conversation comment. This tool cannot update pull request review comments.","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"New comment content","minLength":1},"comment_id":{"type":"integer","description":"The numeric ID of the issue or pull request conversation comment to update. Do not use a pull request review comment ID.","minimum":1},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","comment_id","body"]},"name":"update_issue_comment","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Edit pull request"},"description":"Update an existing pull request in a GitHub repository.","inputSchema":{"type":"object","properties":{"base":{"type":"string","description":"New base branch name"},"body":{"type":"string","description":"New description"},"draft":{"type":"boolean","description":"Mark pull request as draft (true) or ready for review (false)"},"maintainer_can_modify":{"type":"boolean","description":"Allow maintainer edits"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number to update"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"reviewers":{"type":"array","items":{"type":"string"},"description":"GitHub usernames or ORG/team-slug team reviewers to request reviews from"},"state":{"type":"string","description":"New state","enum":["open","closed"]},"title":{"type":"string","description":"New title"}},"required":["owner","repo","pullNumber"]},"name":"update_pull_request","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Update pull request branch"},"description":"Update the branch of a pull request with the latest changes from the base branch.","inputSchema":{"type":"object","properties":{"expectedHeadSha":{"type":"string","description":"The expected SHA of the pull request's HEAD ref"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","pullNumber"]},"name":"update_pull_request_branch","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]}]}} | |
| 3 | + | |
| new file mode 100644 | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | +event: message | ||
| 2 | +data: {"jsonrpc":"2.0","id":1,"result":{"ttlMs":0,"cacheScope":"public","tools":[{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Add review comment to the requester's latest pending pull request review"},"description":"Add review comment to the requester's latest pending pull request review. A pending review needs to already exist to call this (check with the user if not sure).","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"The text of the review comment"},"line":{"type":"number","description":"The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"path":{"type":"string","description":"The relative path to the file that necessitates a comment"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"side":{"type":"string","description":"The side of the diff to comment on. LEFT indicates the previous state, RIGHT indicates the new state","enum":["LEFT","RIGHT"]},"startLine":{"type":"number","description":"For multi-line comments, the first line of the range that the comment applies to"},"startSide":{"type":"string","description":"For multi-line comments, the starting side of the diff that the comment applies to. LEFT indicates the previous state, RIGHT indicates the new state","enum":["LEFT","RIGHT"]},"subjectType":{"type":"string","description":"The level at which the comment is targeted","enum":["FILE","LINE"]}},"required":["owner","repo","pullNumber","path","body","subjectType"]},"name":"add_comment_to_pending_review","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Add comment to issue or pull request"},"description":"Add a comment and/or reaction to a specific issue or issue comment in a GitHub repository. Use this tool with pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add or react to review comments. At least one of body or reaction is required.","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"Comment content. Required unless reaction is provided.","minLength":1},"comment_id":{"type":"integer","description":"The numeric ID of the issue or pull request comment to react to. Use this for reactions to comments; omit it to react to the issue or pull request itself. Cannot be combined with body.","minimum":1},"issue_number":{"type":"number","description":"Issue or pull request number to comment on or react to."},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"reaction":{"type":"string","description":"Emoji reaction to add. Required unless body is provided.","enum":["+1","-1","laugh","confused","heart","hooray","rocket","eyes"]},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","issue_number"]},"name":"add_issue_comment","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Add reply to pull request comment"},"description":"Add a reply and/or reaction to an existing pull request comment. This can create a new comment linked as a reply to the specified comment, add an emoji reaction to the specified comment, or do both. At least one of body or reaction is required.","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"The text of the reply. Required unless reaction is provided."},"commentId":{"type":"number","description":"The numeric ID of the pull request review comment to reply or react to. Use the number from a #discussion_r... anchor, not the GraphQL thread node ID (PRRT_...).","minimum":1},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number. Required when body is provided."},"reaction":{"type":"string","description":"Emoji reaction to add. Required unless body is provided.","enum":["+1","-1","laugh","confused","heart","hooray","rocket","eyes"]},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","commentId"]},"name":"add_reply_to_pull_request_comment","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":true,"readOnlyHint":false,"title":"Assign Copilot to issue"},"description":"Assign Copilot to a specific issue in a GitHub repository.\n\nThis tool can help with the following outcomes:\n- a Pull Request created with source code changes to resolve the issue\n\n\nMore information can be found at:\n- https://docs.github.com/en/copilot/concepts/agents/cloud-agent/about-cloud-agent\n","inputSchema":{"type":"object","properties":{"base_ref":{"type":"string","description":"Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch"},"custom_instructions":{"type":"string","description":"Optional custom instructions to guide the agent beyond the issue body. Use this to provide additional context, constraints, or guidance that is not captured in the issue description"},"issue_number":{"type":"number","description":"Issue number"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","issue_number"]},"name":"assign_copilot_to_issue","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Create branch"},"description":"Create a new branch in a GitHub repository","inputSchema":{"type":"object","properties":{"branch":{"type":"string","description":"Name for new branch"},"from_branch":{"type":"string","description":"Source branch (defaults to repo default)"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","branch"]},"name":"create_branch","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Create or update file"},"description":"Create or update a single file in a GitHub repository. \nIf updating, you should provide the SHA of the file you want to update. Use this tool to create or update a file in a GitHub repository remotely; do not use it for local file operations.\n\nTo obtain the current blob SHA before updating, call the get_file_contents tool with the same owner, repo, and path, and set its ref parameter to this tool's branch value. The first text result reports the blob SHA for the requested path.\n\nSHA MUST be provided for existing file updates.\n","inputSchema":{"type":"object","properties":{"allow_symlink_write":{"type":"boolean","description":"Set true to update a symbolic link itself; content must be its new target path.","default":false},"branch":{"type":"string","description":"Branch to create/update the file in"},"content":{"type":"string","description":"Content of the file, exactly as it should appear once written. Do not base64-encode it; this server does that before calling the REST API."},"message":{"type":"string","description":"Commit message"},"owner":{"type":"string","description":"Repository owner (username or organization)","x-mcp-header":"owner"},"path":{"type":"string","description":"Path where to create/update the file"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sha":{"type":"string","description":"The blob SHA of the file being replaced. Required if the file already exists. Retrieve it with get_file_contents using the same owner, repo, and path, with ref set to this tool's branch value."}},"required":["owner","repo","path","content","message","branch"]},"name":"create_or_update_file","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Open new pull request"},"description":"Create a new pull request in a GitHub repository.","inputSchema":{"type":"object","properties":{"base":{"type":"string","description":"Branch to merge into"},"body":{"type":"string","description":"PR description"},"draft":{"type":"boolean","description":"Create as draft PR"},"head":{"type":"string","description":"Branch containing changes"},"maintainer_can_modify":{"type":"boolean","description":"Allow maintainer edits"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"reviewers":{"type":"array","items":{"type":"string"},"description":"GitHub usernames or ORG/team-slug team reviewers to request reviews from"},"title":{"type":"string","description":"PR title"}},"required":["owner","repo","title","head","base"]},"name":"create_pull_request","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"openWorldHint":false,"readOnlyHint":false,"title":"Perform task with GitHub Copilot coding agent"},"description":"Delegate a task to GitHub Copilot coding agent to perform in the background. The agent will create a pull request with the implementation. You should use this tool if the user asks to create a pull request to perform a specific task, or if the user asks Copilot to do something.","inputSchema":{"type":"object","properties":{"base_ref":{"type":"string","description":"Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch"},"owner":{"type":"string","description":"Repository owner. You can guess the owner, but confirm it with the user before proceeding.","x-mcp-header":"owner"},"problem_statement":{"type":"string","description":"Detailed description of the task to be performed (e.g., 'Implement a feature that does X', 'Fix bug Y', etc.)"},"repo":{"type":"string","description":"Repository name. You can guess the repository name, but confirm it with the user before proceeding.","x-mcp-header":"repo"},"title":{"type":"string","description":"Title for the pull request that will be created"}},"required":["owner","repo","problem_statement","title"]},"name":"create_pull_request_with_copilot","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Create repository"},"description":"Create a new GitHub repository in your account or specified organization","inputSchema":{"type":"object","properties":{"autoInit":{"type":"boolean","description":"Initialize with README"},"description":{"type":"string","description":"Repository description"},"name":{"type":"string","description":"Repository name"},"organization":{"type":"string","description":"Organization to create the repository in (omit to create in your personal account)"},"private":{"type":"boolean","description":"Whether the repository should be private. Defaults to true (private) when omitted.","default":true}},"required":["name"]},"name":"create_repository","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"destructiveHint":true,"idempotentHint":false,"readOnlyHint":false,"title":"Delete file"},"description":"Delete a file from a GitHub repository","inputSchema":{"type":"object","properties":{"branch":{"type":"string","description":"Branch to delete the file from"},"message":{"type":"string","description":"Commit message"},"owner":{"type":"string","description":"Repository owner (username or organization)","x-mcp-header":"owner"},"path":{"type":"string","description":"Path to the file to delete"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","path","message","branch"]},"name":"delete_file","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Fork repository"},"description":"Fork a GitHub repository to your account or specified organization","inputSchema":{"type":"object","properties":{"organization":{"type":"string","description":"Organization to fork to"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"fork_repository","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACuElEQVRIibWTTUhUYRiFn/fOdYyoydQxk4LEGzN3RudaLYL+qRaBQYsIItoHCW37ISNbRwUFLWoRZBEt+4EIooKoTdZQ6TWaNIgouzJkuGhG731b6JTojDNBntX3ne+c97zfH8wzZCbREm9bZ4hsQvkeDvl3+/r6xuYqEIvFFgdSvRuDqCrPMu6bVyUDrITTjdI1jR8KBbrj/fs3Q8WLp5p9Qx4BzVOUInIm058+XdAY0ztH6RLhSpAza1RlI2jENzhfqntfjAugEdTYMFEtS0GvonrKslNrZwWIhDYDMh6Wo4ODvaMfB9LPFaMHZGvJ8xHdAlzPDLx+8Smd/pE39SggAptnB2gwDBD6ReJvhSCpMFyq/uSa/NFX5UMJgGCaxywMwiH/bi4wh0SCOy1x5waiCUF2gnSW3AByEfSSZTsPVXFF9CDC4ALx7xU0ocLA87x8tG7ZHRUShsheVMKInMy46culArIj317WRpd7KB2GsAl4bKoccN2330t5ALBsJ7ASTvecoun6hNNt2U5QbM0oRip8E6Wt0gCUFPC12FKoGFnX0BgBDtVGG3/W1qzqz2a/5IrpLGt9pLahvhPhCKrnsiPDT2dqZv1kgGQyGc4FZg+wr8I93F6y0DzY29s7XlHAnw7j7dswgg2oRCYZPTBluzk51VEwXmQG0k8qbGRuWHbqiWWn/qlY0Uv+n5j3gKKvaCaSyeSimrqms4hsB4kurW9c0bSs/pnneflyXrOcACCn5jWEPSr0AAgczvlVTVT+ykojFlvTZNmOWvHU8QJnJVInLNtR2163vJy/7B0EpjYAqBhugVMVF8A3goZy/rJHFGa8P4fpCXosHm9PqwbiwzHAqyLvlvPP+dEKWG23dyh6C1g0RY0Jsv+Dm77/XwIAWlpbVzJh7gLAnHjw8d27z5V65xW/AVGM6Ekx9nZCAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABoUlEQVRIibWUPS+EQRSFz0hsxaIgIZHYllDYiiiUEn6Bn0Dho6Nhf4GPjkYn8ZEgGqFRSNBoVTQKdhEROsk+mrt2svvu7Gutk7yZzJlz77nzztyR/hmulADSkkYk5SQdO+c+QwmAZkkTktolXTjnbkLiDJCniHsgFdCnTFNAHliuJE6bYANoAYaBF+AwYHBkmiGgFdi0HINR4lmrotXjVoG3gMEbsOLN2yzHTIFr8PRZG3s9rs/jo5At0fd6fFk1TfY/X4A14MyqmQrsYNo0pxbzCtwBTZUCUsAh8GHCKaDspnl6ZyZ3FnMA9AR2/BOYBzJVhUV9BshHrTVEkZKeJPXHNZA0IOkxttrrhzkgGdAlgXnTLv3GIAHsEh87QGNUrooHaEajkoYlFXYxaeO2je+SLp1z57Grr2J4DvwqWaVDrhv+3SAWrMvXgWcgZ10b3a01GuwDX8CWfV/AXr2Sd9lVXPC4ReM6q8XHOYMOG2897rZkrXZY0+WAK6DHHsRr4xJ/NjCTcXstC/gAxuPEBju5xKRb0phNT5xzD7UUW3d8A4p92DZKdSwEAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get commit details"},"description":"Get details for a commit from a GitHub repository","inputSchema":{"type":"object","properties":{"detail":{"type":"string","description":"Level of detail to include for changed files. \"none\" omits stats and files entirely. \"stats\" (default) includes per-file metadata: filename, status, and lines-of-code counts (additions, deletions, changes), with no patch content. \"full_patch\" additionally includes the unified diff content for each file and can be very large.","default":"stats","enum":["none","stats","full_patch"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sha":{"type":"string","description":"Commit SHA, branch name, or tag name"}},"required":["owner","repo","sha"]},"name":"get_commit","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"openWorldHint":false,"readOnlyHint":true,"title":"Get Copilot coding agent job status"},"description":"Get the status of a GitHub Copilot coding agent job. Use this to check if a previously submitted task has completed and to get the pull request URL once it's created. Provide the job ID (from create_pull_request_with_copilot) or pull request number (from assign_copilot_to_issue), or any pull request you want agent sessions for.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Job ID or pull request number"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","id"]},"name":"get_copilot_job_status","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get file or directory contents"},"description":"Get the contents of a file or directory from a GitHub repository","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["type","name","path","size","sha","url","git_url","html_url","download_url"]},"description":"Subset of fields to return for each entry when the path is a directory. If omitted, all fields are returned. Ignored when the path is a single file. Use this to reduce response size when listing directories and you only need specific fields, e.g. just 'name' and 'type'."},"owner":{"type":"string","description":"Repository owner (username or organization)","x-mcp-header":"owner"},"path":{"type":"string","description":"Path to file/directory","default":"/"},"ref":{"type":"string","description":"Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sha":{"type":"string","description":"Accepts optional commit SHA. If specified, it will be used instead of ref"}},"required":["owner","repo"]},"name":"get_file_contents","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get a specific label from a repository"},"description":"Get a specific label from a repository.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Label name."},"owner":{"type":"string","description":"Repository owner (username or organization name)","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","name"]},"name":"get_label","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get latest release"},"description":"Get the latest release in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"get_latest_release","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get my user profile"},"description":"Get details of the authenticated GitHub user. Use this when a request is about the user's own profile for GitHub. Or when information is missing to build other tool calls.","inputSchema":{"type":"object","properties":{}},"name":"get_me","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACSUlEQVRIidWVv09TURTHP+e+2mJMEOLiQBig4GtN4WEHRQxGMfHHYkx0YpJJR+OmjMTNP0CjRv4DJyNETXTQODRQSkmJTweYBUw0KeX1HoeCKY3v0eKi3/Gdc74/zrs3F/53SJN9Jul6owhpAJQlvzT/HrB/LZB0h4YR+wjI7B6UBVW55ZfmPkY6a4J8FugQ1euOLbc7ttyuyDWLJhA7k0wNnNpvAulLefOKdiZM4BWLxbX6Yncm0xkPTB5lzS/lhwBtKUHS9c4qOiCqdxrJAVYKhXVE7iIMJt2h0TCe8BVt/1Cjm7OhPZXETK23mm5ZQMTGQom3YW0gACoS2hsqoNbJAwTm4FjocFtwEcCozYcajTBo+lLenEUTWzE7vFIorNcXXdc9EpCYB775pfwJQu5E1BqsVW6L8CoemHwy7d39vfN4+VJgeYhwGPRGGPleCQCkN+XdE3Tqz1W97y8tPIgkCCv092dc68gzkGFgHXiNsFrLJt2IjgGdwAfH6sTy8sJy0wK9xwbPieEFSlmRyY5DzvNcLrdV35PNZg9s/KzeFNEpII6aq35p7t2eAjXn5hOwqk718pfFxdXQ/EDP8Wy3scFLoMuxerIxSeMxFeuYpyjlZsgBvhZzK9bErgAVa+RJo+ldAn0p7wJwWpHJZsjrRVRlUuFMT3rgfEQCOw5stDlb082S70CCH9PAd2NlPFRAkRGEN8VisdKqgO/7mwJvEUZCBYCjwOdWyXdga7Nd9d9232SRCSo28oWKgjjVxxrElvY7/2/iF/Bu47CZ2fOnAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABaElEQVRIidWUvUrDcBTFz79YnRSii4N2cVB8AuvgByKuQqmrILQPoe9SfABxV5wUna2IVLq1k11adffn4A3UNPknjQ56IARyzz3ncj8i/Xe4LCSgIGlD0qp9epJ07Zz7+HEFQBl4YBRNoPwb4u9AB6gA0/bsAy3gDVjLK+6syg4wGxMPLHYPZGp1VGDLWlHxcKrG2UziFDwe4UAvPZyLCHcsgwlPLETYmkSuz6Bp7x0PZy/CzQ6gYENuAUFMfA7o2pB9hXpN1m0VOzbQGXsOTDz/mpqBA05ijizEcZpG4v4CK5IaksqS+pKuJHUtXNLXbAJJd5KOnHPP41S+DbwCL0ANKMZwikAd6AED3y2MVG7ij8BiBn7JuANgOY3sgFurPFU8YtIDbry/DWDXhlfLKj6UW7fc5LsBToE+MJnDYMra1PCR2sDZuOJD+efAt22KXuC8pHZeA8td8FVQBZIJKQCWgMO8+X8Tn12zhtgfmPjeAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get a release by tag name"},"description":"Get a specific release by its tag name in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"tag":{"type":"string","description":"Tag name (e.g., 'v1.0.0')"}},"required":["owner","repo","tag"]},"name":"get_release_by_tag","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get tag details"},"description":"Get details about a specific git tag in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"tag":{"type":"string","description":"Tag name"}},"required":["owner","repo","tag"]},"name":"get_tag","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get team members"},"description":"Get member usernames of a specific team in an organization. Limited to organizations accessible with current credentials","inputSchema":{"type":"object","properties":{"org":{"type":"string","description":"Organization login (owner) that contains the team."},"team_slug":{"type":"string","description":"Team slug"}},"required":["org","team_slug"]},"name":"get_team_members","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACSUlEQVRIidWVv09TURTHP+e+2mJMEOLiQBig4GtN4WEHRQxGMfHHYkx0YpJJR+OmjMTNP0CjRv4DJyNETXTQODRQSkmJTweYBUw0KeX1HoeCKY3v0eKi3/Gdc74/zrs3F/53SJN9Jul6owhpAJQlvzT/HrB/LZB0h4YR+wjI7B6UBVW55ZfmPkY6a4J8FugQ1euOLbc7ttyuyDWLJhA7k0wNnNpvAulLefOKdiZM4BWLxbX6Yncm0xkPTB5lzS/lhwBtKUHS9c4qOiCqdxrJAVYKhXVE7iIMJt2h0TCe8BVt/1Cjm7OhPZXETK23mm5ZQMTGQom3YW0gACoS2hsqoNbJAwTm4FjocFtwEcCozYcajTBo+lLenEUTWzE7vFIorNcXXdc9EpCYB775pfwJQu5E1BqsVW6L8CoemHwy7d39vfN4+VJgeYhwGPRGGPleCQCkN+XdE3Tqz1W97y8tPIgkCCv092dc68gzkGFgHXiNsFrLJt2IjgGdwAfH6sTy8sJy0wK9xwbPieEFSlmRyY5DzvNcLrdV35PNZg9s/KzeFNEpII6aq35p7t2eAjXn5hOwqk718pfFxdXQ/EDP8Wy3scFLoMuxerIxSeMxFeuYpyjlZsgBvhZzK9bErgAVa+RJo+ldAn0p7wJwWpHJZsjrRVRlUuFMT3rgfEQCOw5stDlb082S70CCH9PAd2NlPFRAkRGEN8VisdKqgO/7mwJvEUZCBYCjwOdWyXdga7Nd9d9232SRCSo28oWKgjjVxxrElvY7/2/iF/Bu47CZ2fOnAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABaElEQVRIidWUvUrDcBTFz79YnRSii4N2cVB8AuvgByKuQqmrILQPoe9SfABxV5wUna2IVLq1k11adffn4A3UNPknjQ56IARyzz3ncj8i/Xe4LCSgIGlD0qp9epJ07Zz7+HEFQBl4YBRNoPwb4u9AB6gA0/bsAy3gDVjLK+6syg4wGxMPLHYPZGp1VGDLWlHxcKrG2UziFDwe4UAvPZyLCHcsgwlPLETYmkSuz6Bp7x0PZy/CzQ6gYENuAUFMfA7o2pB9hXpN1m0VOzbQGXsOTDz/mpqBA05ijizEcZpG4v4CK5IaksqS+pKuJHUtXNLXbAJJd5KOnHPP41S+DbwCL0ANKMZwikAd6AED3y2MVG7ij8BiBn7JuANgOY3sgFurPFU8YtIDbry/DWDXhlfLKj6UW7fc5LsBToE+MJnDYMra1PCR2sDZuOJD+efAt22KXuC8pHZeA8td8FVQBZIJKQCWgMO8+X8Tn12zhtgfmPjeAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get teams"},"description":"Get details of the teams the user is a member of. Limited to organizations accessible with current credentials","inputSchema":{"type":"object","properties":{"user":{"type":"string","description":"Username to get teams for. If not provided, uses the authenticated user."}}},"name":"get_teams","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACSUlEQVRIidWVv09TURTHP+e+2mJMEOLiQBig4GtN4WEHRQxGMfHHYkx0YpJJR+OmjMTNP0CjRv4DJyNETXTQODRQSkmJTweYBUw0KeX1HoeCKY3v0eKi3/Gdc74/zrs3F/53SJN9Jul6owhpAJQlvzT/HrB/LZB0h4YR+wjI7B6UBVW55ZfmPkY6a4J8FugQ1euOLbc7ttyuyDWLJhA7k0wNnNpvAulLefOKdiZM4BWLxbX6Yncm0xkPTB5lzS/lhwBtKUHS9c4qOiCqdxrJAVYKhXVE7iIMJt2h0TCe8BVt/1Cjm7OhPZXETK23mm5ZQMTGQom3YW0gACoS2hsqoNbJAwTm4FjocFtwEcCozYcajTBo+lLenEUTWzE7vFIorNcXXdc9EpCYB775pfwJQu5E1BqsVW6L8CoemHwy7d39vfN4+VJgeYhwGPRGGPleCQCkN+XdE3Tqz1W97y8tPIgkCCv092dc68gzkGFgHXiNsFrLJt2IjgGdwAfH6sTy8sJy0wK9xwbPieEFSlmRyY5DzvNcLrdV35PNZg9s/KzeFNEpII6aq35p7t2eAjXn5hOwqk718pfFxdXQ/EDP8Wy3scFLoMuxerIxSeMxFeuYpyjlZsgBvhZzK9bErgAVa+RJo+ldAn0p7wJwWpHJZsjrRVRlUuFMT3rgfEQCOw5stDlb082S70CCH9PAd2NlPFRAkRGEN8VisdKqgO/7mwJvEUZCBYCjwOdWyXdga7Nd9d9232SRCSo28oWKgjjVxxrElvY7/2/iF/Bu47CZ2fOnAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABaElEQVRIidWUvUrDcBTFz79YnRSii4N2cVB8AuvgByKuQqmrILQPoe9SfABxV5wUna2IVLq1k11adffn4A3UNPknjQ56IARyzz3ncj8i/Xe4LCSgIGlD0qp9epJ07Zz7+HEFQBl4YBRNoPwb4u9AB6gA0/bsAy3gDVjLK+6syg4wGxMPLHYPZGp1VGDLWlHxcKrG2UziFDwe4UAvPZyLCHcsgwlPLETYmkSuz6Bp7x0PZy/CzQ6gYENuAUFMfA7o2pB9hXpN1m0VOzbQGXsOTDz/mpqBA05ijizEcZpG4v4CK5IaksqS+pKuJHUtXNLXbAJJd5KOnHPP41S+DbwCL0ANKMZwikAd6AED3y2MVG7ij8BiBn7JuANgOY3sgFurPFU8YtIDbry/DWDXhlfLKj6UW7fc5LsBToE+MJnDYMra1PCR2sDZuOJD+efAt22KXuC8pHZeA8td8FVQBZIJKQCWgMO8+X8Tn12zhtgfmPjeAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get issue details"},"description":"Get information about a specific issue in a GitHub repository.","inputSchema":{"type":"object","properties":{"issue_number":{"type":"number","description":"The number of the issue"},"method":{"type":"string","description":"The read operation to perform on a single issue.\nOptions are:\n1. get - Get issue details. Also returns best-effort hierarchy flags (`has_parent`, `has_children`); `parent` and `sub_issues_summary` are optional relationship summaries, and `closed_by_pull_requests` summarizes the pull requests configured to close the issue as `total_count` plus up to 5 `references`.\n2. get_comments - Get issue comments.\n3. get_sub_issues - Get sub-issues (children) of the issue.\n4. get_parent - Get the parent issue, if this issue is a sub-issue of another.\n5. get_labels - Get labels assigned to the issue.\n","enum":["get","get_comments","get_sub_issues","get_parent","get_labels"]},"owner":{"type":"string","description":"The owner of the repository","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"The name of the repository","x-mcp-header":"repo"}},"required":["method","owner","repo","issue_number"]},"name":"issue_read","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Create or update issue/pull request"},"description":"Create a new or update an existing issue in a GitHub repository.","inputSchema":{"type":"object","properties":{"assignees":{"type":"array","items":{"type":"string"},"description":"Usernames to assign to this issue"},"body":{"type":"string","description":"Issue body content"},"duplicate_of":{"type":"number","description":"Issue number that this issue is a duplicate of. Required when state_reason is 'duplicate'."},"issue_fields":{"type":"array","items":{"type":"object","properties":{"delete":{"type":"boolean","description":"Set to true to clear this field's current value on the issue. When false or omitted, this property is ignored. Cannot be true when 'value' or 'field_option_name' is provided."},"field_name":{"type":"string","description":"Issue field name (case-insensitive). Must match a field returned by list_issue_fields for this repository or its organization."},"field_option_name":{"type":"string","description":"Option name for single-select fields. Validated against the field's options before the API call. Cannot be combined with 'value' or 'delete: true'."},"value":{"type":["string","number","boolean"],"description":"Value to set. Use for text, number, and date fields (date as YYYY-MM-DD). For single-select fields, prefer 'field_option_name' so the option is validated before the API call. Cannot be combined with 'field_option_name' or 'delete: true'."}},"required":["field_name"],"additionalProperties":false},"description":"Issue field values to set or clear. Each item requires 'field_name' and exactly one of 'value', 'field_option_name', or 'delete: true'."},"issue_number":{"type":"number","description":"Issue number to update"},"labels":{"type":"array","items":{"type":"string"},"description":"Labels to apply to this issue"},"method":{"type":"string","description":"Write operation to perform on a single issue.\nOptions are:\n- 'create' - creates a new issue.\n- 'update' - updates an existing issue.\n","enum":["create","update"]},"milestone":{"type":"number","description":"Milestone number"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"parent_issue_number":{"type":"number","description":"Issue number of the parent issue. Only used when method is 'create' and cannot be combined with issue_fields. The new issue is created and attached to this parent in the same operation.","minimum":1},"parent_owner":{"type":"string","description":"Repository owner of the parent issue. Must be provided with parent_repo. Omit both to use owner and repo. Only used when method is 'create' and parent_issue_number is provided."},"parent_repo":{"type":"string","description":"Repository name of the parent issue. Must be provided with parent_owner. Omit both to use owner and repo. Only used when method is 'create' and parent_issue_number is provided."},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"state":{"type":"string","description":"New state","enum":["open","closed"]},"state_reason":{"type":"string","description":"Reason for the state change. Ignored unless state is changed.","enum":["completed","not_planned","duplicate"]},"title":{"type":"string","description":"Issue title"},"type":{"description":"Type of this issue. For updates, pass null to remove the current type. Only use if issue types are enabled for this repository. Use list_issue_types to get valid type values for this repository or its owner organization. If the repository doesn't support issue types, omit this parameter.","anyOf":[{"type":"string","minLength":1},{"type":"null"}]}},"required":["method","owner","repo"]},"name":"issue_write","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List branches"},"description":"List branches in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"list_branches","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List commits"},"description":"Get list of commits of a branch in a GitHub repository. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).","inputSchema":{"type":"object","properties":{"author":{"type":"string","description":"Author username or email address to filter commits by"},"fields":{"type":"array","items":{"type":"string","enum":["sha","html_url","commit","author","committer"]},"description":"Subset of fields to return for each commit. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields, e.g. just 'sha' and 'html_url'."},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"path":{"type":"string","description":"Only commits containing this file path will be returned"},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sha":{"type":"string","description":"Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA."},"since":{"type":"string","description":"Only commits after this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)"},"until":{"type":"string","description":"Only commits before this date will be returned (ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ or YYYY-MM-DD)"}},"required":["owner","repo"]},"name":"list_commits","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List issue fields"},"description":"List issue fields for a repository or organization. Returns field definitions including name, type (text, number, date, single_select), and for single_select fields the list of valid option names. When repo is omitted, returns org-level fields directly.","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"The account owner of the repository or organization. The name is not case sensitive.","x-mcp-header":"owner"},"repo":{"type":"string","description":"The name of the repository. When provided, returns fields for this specific repository (inherited from its organization). When omitted, returns org-level fields directly.","x-mcp-header":"repo"}},"required":["owner"]},"name":"list_issue_fields","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List available issue types"},"description":"List supported issue types for a repository or its owner organization. When repo is omitted, returns org-level issue types directly.","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"The account owner of the repository or organization.","x-mcp-header":"owner"},"repo":{"type":"string","description":"The name of the repository. When provided, returns issue types for this specific repository. When omitted, returns org-level issue types directly.","x-mcp-header":"repo"}},"required":["owner"]},"name":"list_issue_types","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List issues"},"description":"List issues in a GitHub repository. For pagination, use the 'endCursor' from the previous response's 'pageInfo' in the 'after' parameter.","inputSchema":{"type":"object","properties":{"after":{"type":"string","description":"Cursor for pagination. Use the cursor from the previous response."},"direction":{"type":"string","description":"Order direction. If provided, the 'orderBy' also needs to be provided.","enum":["ASC","DESC"]},"field_filters":{"type":"array","items":{"type":"object","properties":{"field_name":{"type":"string","description":"Name of the custom field (e.g. \"Priority\"). Case-insensitive."},"value":{"type":"string","description":"Value to filter on. For single-select fields, the option name (e.g. \"P1\"). For dates, YYYY-MM-DD. For numbers, the numeric value as a string. For text, the text value."}},"required":["field_name","value"]},"description":"Filter by custom issue field values. Each entry takes a field_name and a value; the server looks up the field and coerces the value to its type (single-select option name, text, number, or YYYY-MM-DD date)."},"fields":{"type":"array","items":{"type":"string","enum":["number","title","body","state","user","labels","assignees","comments","created_at","updated_at","field_values"]},"description":"Subset of fields to return for each issue. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body' and 'field_values' in particular drops the largest per-result data."},"labels":{"type":"array","items":{"type":"string"},"description":"Filter by labels"},"orderBy":{"type":"string","description":"Order issues by field. If provided, the 'direction' also needs to be provided.","enum":["CREATED_AT","UPDATED_AT","COMMENTS"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"since":{"type":"string","description":"Filter by date (ISO 8601 timestamp)"},"state":{"type":"string","description":"Filter by state, by default both open and closed issues are returned when not provided","enum":["OPEN","CLOSED"]}},"required":["owner","repo"]},"name":"list_issues","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List pull requests"},"description":"List pull requests in a GitHub repository. If the user specifies an author, then DO NOT use this tool and use the search_pull_requests tool instead.","inputSchema":{"type":"object","properties":{"base":{"type":"string","description":"Filter by base branch"},"direction":{"type":"string","description":"Sort direction","enum":["asc","desc"]},"fields":{"type":"array","items":{"type":"string","enum":["number","title","body","state","draft","merged","mergeable_state","html_url","user","labels","assignees","requested_reviewers","merged_by","head","base","additions","deletions","changed_files","commits","comments","created_at","updated_at","closed_at","merged_at","milestone"]},"description":"Subset of fields to return for each pull request. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body' in particular drops the largest per-result data."},"head":{"type":"string","description":"Filter by head user/org and branch"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sort":{"type":"string","description":"Sort by","enum":["created","updated","popularity","long-running"]},"state":{"type":"string","description":"Filter by state","enum":["open","closed","all"]}},"required":["owner","repo"]},"name":"list_pull_requests","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List releases"},"description":"List releases in a GitHub repository","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["id","tag_name","name","body","html_url","published_at","prerelease","draft","author"]},"description":"Subset of fields to return for each release. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body' in particular drops the largest per-release data."},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"list_releases","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List repository collaborators"},"description":"List collaborators of a GitHub repository. Results are paginated; the response includes `nextPage`, `prevPage`, `firstPage`, and `lastPage` fields. To get the next page, use the `nextPage` value as the `page` parameter.","inputSchema":{"type":"object","properties":{"affiliation":{"type":"string","description":"Filter by affiliation. Can be one of: 'outside' (outside collaborators), 'direct' (all with permissions regardless of org membership), 'all' (all collaborators). Default: 'all'","enum":["outside","direct","all"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (default 1, min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (default 30, min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"list_repository_collaborators","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"List tags"},"description":"List git tags in a GitHub repository","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo"]},"name":"list_tags","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Merge pull request"},"description":"Merge a pull request in a GitHub repository.","inputSchema":{"type":"object","properties":{"commit_message":{"type":"string","description":"Extra detail for merge commit"},"commit_title":{"type":"string","description":"Title for merge commit"},"expectedHeadSha":{"type":"string","description":"The expected SHA of the pull request's HEAD ref"},"merge_method":{"type":"string","description":"Merge method","enum":["merge","squash","rebase"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","pullNumber"]},"name":"merge_pull_request","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACeElEQVRIibWVTUhUYRSGn/e74+iiQih1F9Vcmj9sptylUVBYkO4jcNeuJBdFKxe1CYQokGrRKjCEdtmqwEVmtqomQWeiUdc2EBUtUufe0yLHn1KLGXtX5zvn4zz3vd8f/Gfp90Qs0drmpA6MT1EveDo1NfV92wB+KnMdo39Nfs4L7eSHD5Nz1QJcJYglWtsw+iUehAuRRjO1g+0KHLerbb4OIHnHAC1FdW129s3XmUJuwnBDoOPbA7BwHsD7QWq1HKYN5msBRCpB1AueLoSROSkciSUyj5ClhE6BLtYC8CpBqVRabNrdMmIiJdQjuUbQ1WI+d78WwIbykxnzU9np7ejlNq2YxQ4ebNtTKyCyWcEgYl55EDj/a7ihFEtkLkr0As2YxjwL+9aem00dCEYNzvnJzLDvH27aaM5y80HEnKGHKGwPnEbT6fSOvzpAmrDQnkncpC7siiUzz2QqIPu25iOuGBorTufO/AJmH0v2ajHwuoHhrQHATOH9rQPJ7IjDLgs6kZ0F6it1AzArVcZLdUE+WnYgmv/uYFmz+dxH4NJGNT+RfYLCE7F4tn0pGkxHy94AmBm8/GfAVvIs7AukUTkbj5YdYIbZ9WJh8m1lzrrbNB4/tD+QuyPsdCibF26gmM/dY/NdRDqd3rEYeN04mswYL+ZXm68DxOPxnWXXMClsp+GGhCWBTtClYj53t1qXK78oVH2XYB/mHZ0pvHsN4Cczzw3rBaoGrJ6D5ZUvN1i+kjI0LWiptjmscbC88hZZCAf2trZeq1v0UsJ6wF7UAlhxUMxPvkW6AboQLbvPcjaO+BIx11cL4I9H308eOiLRQUhpOx79/66fNKzrOCYNDm0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABjElEQVRIibWVPS/DURTGnysSC0HiZdWVrZ28JDaLT8BHaBsMdjqZJDXiAzC2LF5mX6GtATGiIsGARH+Gnj9X8a/kf3uWe3Py3Oc559xz75E6bK7VAWQkzUi6lXTonHsOpgYUgAZfdgmkQpFnjHwb6AemgDpQCiWwYlEPeL4i8JCEt8vb39g67vkmPH8yA3qt5nVgCzi1jLJBBEwkBZSAdxPKAj86LYQQQCU4cYvAKzDUSYF3YC+uRIAD8sA58ACU//VuTODE1n1g+A9c3jBH1tJ1a5TeCPNrdACSCpKeJG1IepN0LKkm6dGDrkqqOOdm7dyUpDNJi865PUnqjsvEObcJHEhaljQnaV5STwvszttXbR2J441KtB4LauLKVpZpYBDYte8mHUogZTWPrAGstTtQBl6AayDX7qHZD7AALMVGDvQBV5ZyETi2qHLtMvmXWRQAk57vBKgl4fV/0+jmq56vImk0icCnAWm7pB3riGngnlADx0TW+T4yL4CxJJy/Df20mkP/TqGHfifsA7INs3X5i3+yAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Get details for a single pull request"},"description":"Get information on a specific pull request in GitHub repository.","inputSchema":{"type":"object","properties":{"after":{"type":"string","description":"Cursor for pagination, used only by the get_review_comments method. Pass the endCursor from the previous page's PageInfo to fetch the next page."},"method":{"type":"string","description":"Action to specify what pull request data needs to be retrieved from GitHub. \nPossible options: \n 1. get - Get details of a specific pull request.\n 2. get_diff - Get the diff of a pull request.\n 3. get_status - Get combined commit status of a head commit in a pull request.\n 4. get_files - Get the list of files changed in a pull request. Use with pagination parameters to control the number of results returned.\n 5. get_commits - Get the list of commits on a pull request. Use with pagination parameters to control the number of results returned.\n 6. get_review_comments - Get review threads on a pull request. Each thread contains logically grouped review comments made on the same code location during pull request reviews. Returns thread metadata and comments with nullable current and original line-range coordinates (line, start_line, original_line, original_start_line). Current coordinates are omitted when unavailable, such as for outdated comments. Use cursor-based pagination (perPage, after) to control results.\n 7. get_reviews - Get the reviews on a pull request. When asked for review comments, use get_review_comments method. Use with pagination parameters to control the number of results returned.\n 8. get_comments - Get comments on a pull request. Use this if user doesn't specifically want review comments. Use with pagination parameters to control the number of results returned.\n 9. get_check_runs - Get check runs for the head commit of a pull request. Check runs are the individual CI/CD jobs and checks that run on the PR.\n","enum":["get","get_diff","get_status","get_files","get_commits","get_review_comments","get_reviews","get_comments","get_check_runs"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["method","owner","repo","pullNumber"]},"name":"pull_request_read","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Write operations (create, submit, delete) on pull request reviews"},"description":"Create and/or submit, delete review of a pull request.\n\nAvailable methods:\n- create: Create a new review of a pull request. If \"event\" parameter is provided, the review is submitted. If \"event\" is omitted, a pending review is created.\n- submit_pending: Submit an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request. The \"body\" and \"event\" parameters are used when submitting the review.\n- delete_pending: Delete an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request.\n- resolve_thread: Resolve a review thread. Requires only \"threadId\" parameter with the thread's node ID (e.g., PRRT_kwDOxxx). The owner, repo, and pullNumber parameters are not used for this method. Resolving an already-resolved thread is a no-op.\n- unresolve_thread: Unresolve a previously resolved review thread. Requires only \"threadId\" parameter. The owner, repo, and pullNumber parameters are not used for this method. Unresolving an already-unresolved thread is a no-op.\n","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"Review comment text"},"commitID":{"type":"string","description":"SHA of commit to review"},"event":{"type":"string","description":"Review action to perform.","enum":["APPROVE","REQUEST_CHANGES","COMMENT"]},"method":{"type":"string","description":"The write operation to perform on pull request review.","enum":["create","submit_pending","delete_pending","resolve_thread","unresolve_thread"]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"threadId":{"type":"string","description":"The node ID of the review thread (e.g., PRRT_kwDOxxx). Required for resolve_thread and unresolve_thread methods. Get thread IDs from pull_request_read with method get_review_comments."}},"required":["method","owner","repo","pullNumber"]},"name":"pull_request_review_write","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Push files to repository"},"description":"Push multiple files to a GitHub repository in a single commit","inputSchema":{"type":"object","properties":{"branch":{"type":"string","description":"Branch to push to"},"files":{"type":"array","items":{"type":"object","properties":{"content":{"type":"string","description":"file content"},"path":{"type":"string","description":"path to the file"}},"required":["path","content"],"additionalProperties":false},"description":"Array of file objects to push, each object with path (string) and content (string)"},"message":{"type":"string","description":"Commit message"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","branch","files","message"]},"name":"push_files","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Request Copilot review"},"description":"Request a GitHub Copilot code review for a pull request. Use this for automated feedback on pull requests, usually before requesting a human reviewer.","inputSchema":{"type":"object","properties":{"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","pullNumber"]},"name":"request_copilot_review","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"openWorldHint":false,"readOnlyHint":true,"title":"Run Secret Scanning"},"description":"Scan files, content, or recent changes for secrets such as API keys, passwords, tokens, and credentials.\n\nThis tool is intended for targeted scans of specific files, snippets, or diffs provided directly as content. The files parameter accepts either a single string or an array of strings containing raw file contents or diff hunks, and returns detected secrets with their locations and related secret scanning metadata. Content must not be empty. For full repository scanning, other mechanisms are available.\n\nCaveats:\n\n- Only files within the codebase should be scanned. Files outside of the codebase should not be sent.\n- Files listed in .gitignore should be skipped.","inputSchema":{"type":"object","properties":{"files":{"description":"A single string or an array of strings containing file contents, snippets, or diff hunks to scan for secrets. These must be raw contents, not repository file paths.","anyOf":[{"type":"string","minLength":1},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100}]},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["files","owner","repo"]},"name":"run_secret_scanning","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search code"},"description":"Fast and precise code search across ALL GitHub repositories using GitHub's native search engine. Best for finding exact symbols, functions, classes, or specific code patterns.","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["name","path","sha","repository","text_matches"]},"description":"Subset of fields to return for each code search result. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'repository' and 'text_matches' in particular drops the largest per-result data."},"order":{"type":"string","description":"Sort order for results","enum":["asc","desc"]},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"Search query (GitHub code search REST). Implicit AND between terms; supports `OR`, `NOT`, and `\"quoted phrase\"` for exact match. Qualifiers: `repo:owner/repo`, `org:`, `user:`, `language:`, `path:dir` (prefix match), `filename:exact.ext`, `extension:`, `in:file`, `in:path`, `size:`, `is:archived`, `is:fork`. Max 256 chars. Examples: `WithContext language:go org:github`; `\"package main\" repo:o/r`; `func extension:go path:cmd repo:o/r`; `NOT TODO language:go repo:o/r`."},"sort":{"type":"string","description":"Sort field ('indexed' only)"}},"required":["query"]},"name":"search_code","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search commits"},"description":"Search for commits across GitHub repositories using GitHub's commit search syntax. Useful for finding specific changes, authors, or messages across one or many repositories. Searches the default branch only.","inputSchema":{"type":"object","properties":{"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"Commit search query (GitHub commit search REST). Searches commit messages on the default branch only. Scope the search with `repo:owner/repo`, `org:`, or `user:` (queries without a scope qualifier match across all of GitHub and are usually not what you want). Other qualifiers: `author:`, `committer:`, `author-name:`, `committer-name:`, `author-email:`, `committer-email:`, `author-date:`, `committer-date:` (supports `\u003e`, `\u003c`, `\u003e=`, `\u003c=`, and `YYYY-MM-DD..YYYY-MM-DD` ranges), `merge:true|false`, `hash:`, `tree:`, `parent:`, `is:public`. Examples: `repo:owner/repo fix panic`; `org:github author:defunkt committer-date:\u003e=2024-01-01`; `\"refactor cache\" repo:o/r`; `hash:abc1234 repo:o/r`."},"sort":{"type":"string","description":"Sort by author or committer date (defaults to best match)","enum":["author-date","committer-date"]}},"required":["query"]},"name":"search_commits","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search issues"},"description":"Search issues using natural-language semantic matching. Best for conceptual or paraphrased queries (e.g. \"login fails after password reset\"). Already scoped to is:issue.","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["number","title","body","state","state_reason","draft","locked","html_url","user","author_association","labels","assignee","assignees","milestone","comments","reactions","created_at","updated_at","closed_at","closed_by","type","repository_url","pull_request","field_values"]},"description":"Subset of fields to return for each issue result. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body', 'reactions', and 'labels' in particular drops the largest per-result data."},"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"owner":{"type":"string","description":"Optional repository owner. If provided with repo, only issues for this repository are listed.","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"The search query, as natural language. When the user gives alternative wordings, include them as plain words rather than joining them with OR."},"repo":{"type":"string","description":"Optional repository name. If provided with owner, only issues for this repository are listed.","x-mcp-header":"repo"},"sort":{"type":"string","description":"Sort field by number of matches of categories, defaults to best match","enum":["comments","reactions","reactions-+1","reactions--1","reactions-smile","reactions-thinking_face","reactions-heart","reactions-tada","interactions","created","updated"]}},"required":["query"]},"name":"search_issues","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search pull requests"},"description":"Search for pull requests in GitHub repositories using issues search syntax already scoped to is:pr","inputSchema":{"type":"object","properties":{"fields":{"type":"array","items":{"type":"string","enum":["number","title","body","state","state_reason","draft","locked","html_url","user","author_association","labels","assignee","assignees","milestone","comments","reactions","created_at","updated_at","closed_at","closed_by","pull_request","repository_url"]},"description":"Subset of fields to return for each pull request result. If omitted, all fields are returned. Use this to reduce response size when you only need specific fields; omitting 'body', 'reactions', and 'labels' in particular drops the largest per-result data."},"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"owner":{"type":"string","description":"Optional repository owner. If provided with repo, only pull requests for this repository are listed.","x-mcp-header":"owner"},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"Search query using GitHub pull request search syntax"},"repo":{"type":"string","description":"Optional repository name. If provided with owner, only pull requests for this repository are listed.","x-mcp-header":"repo"},"sort":{"type":"string","description":"Sort field by number of matches of categories, defaults to best match","enum":["comments","reactions","reactions-+1","reactions--1","reactions-smile","reactions-thinking_face","reactions-heart","reactions-tada","interactions","created","updated"]}},"required":["query"]},"name":"search_pull_requests","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search repositories"},"description":"Find GitHub repositories by name, description, readme, topics, or other metadata. Perfect for discovering projects, finding examples, or locating specific repositories across GitHub.","inputSchema":{"type":"object","properties":{"minimal_output":{"type":"boolean","description":"Return minimal repository information (default: true). When false, returns full GitHub API repository objects.","default":true},"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"Repository search query. Examples: 'machine learning in:name stars:\u003e1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering."},"sort":{"type":"string","description":"Sort repositories by field, defaults to best match","enum":["stars","forks","help-wanted-issues","updated"]}},"required":["query"]},"name":"search_repositories","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":true,"title":"Search users"},"description":"Find GitHub users by username, real name, or other profile information. Useful for locating developers, contributors, or team members.","inputSchema":{"type":"object","properties":{"order":{"type":"string","description":"Sort order","enum":["asc","desc"]},"page":{"type":"number","description":"Page number for pagination (min 1)","minimum":1},"perPage":{"type":"number","description":"Results per page for pagination (min 1, max 100)","minimum":1,"maximum":100},"query":{"type":"string","description":"User search query. Examples: 'john smith', 'location:seattle', 'followers:\u003e100'. Search is automatically scoped to type:user."},"sort":{"type":"string","description":"Sort users by number of followers or repositories, or when the person joined GitHub.","enum":["followers","repositories","joined"]}},"required":["query"]},"name":"search_users","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAADWklEQVRIidWTT2hcVRTGf+e+mUn9Q3WMqbbWgJ1JnMw4meiAUkoxIRG1IKK4ELqoVkQXUsVuXbropoq4EWykuBDURV2I0r9GrC0VopnXTGaefYkhDahBOg4SnSTv3eMiTphMJ6Spq57dPd+933e/794DN3rJWkA+n49W54MB4AE1aoyawqVS1xn4PPzfAsnUg48iOgyaaIJ8I7r/5wn3u2sVMM2N7nTvbsQeB42J6nOOrW12bG2zIs8ohFblZHe6d/d1Oejv74/MzlU8lGibCfqKxeKVRrwzm43HAlMAatvviqdHRkaC9QScxkVk021DwAFRfalcGv+xeXN1bq52x5ats8Crf80vnL3yx29TiZ7e59s7tg7Ft3TEc5n09PT0tG08syoiEckBGF04seaVFtuOA1ixuf9OvQP6rqj5avb3SmlHKptdU0BhaT3L1gbLsVpRgMlSYdvNUb1VlKeAqBFzsjObjbcWMNYFCMxNg2sJmE3B4wCOmEK957ru/KVy4UtxnCeBO2OheaOONX9Tk+zJlRXsUsTunLl4sdIIplKp9oC2MYR//FJ3T6uZSKZzZ7Es+OXCIECkOQFVeUFET8UCU0im+w7WMydWeyKwHEboUCuDaw2cKGpFVx76qjmYLI+dRzkE3IvqZ0RrVaK1KsqnCNsROTTpjX3fijyReSip8LCo+aFlRN3d2ZR15COQnUAFOIVwedmbdCI6CMSBc47V/Z7neivkqWxexHwC2h4lmi2VRn9dJZC4Pzcghi9Qaoq8dfstztHR0dFVvyqfz0f/nA9fFNG3gRhqnvbLP30LkOzJfQP0Kzw7WSocW+Vg+ebmAjCjTrhncnz8cqsI6rUjk+80NvgauMex+ojnuV4ynTuMcgD4G/RNv+QO199ArGOGUWrXQg4wVRydCTF7gCVr5Agg/kThIE6YQjkH8mEi1bcPQLp6+h5T9AToy37JPbIeeWN1pXOvqPKBFR2amnBPA2QymdiidY4pMmhCEgbsXqDSZsKPN0IOoIvzR4GqsbK33isWi4vG8hogoaOvG0V2oXK6WCwublTA9/0FgTMIuxr7nuf+AowIDBjgboz6GyVfcSF4wLarAGEcuC8iyj4JuXC9Ak5o3g8j4fnmvpXIe44NWg7kjVX/Ap7dYx0LcmfJAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAB20lEQVRIidXUvUuVcRQH8PPc1JYUrKWUDCmwoKa2QsTKoKEwxWhr6G1tqDVqaAn8F6I5oS0ipBp6WZqiAi1bEgIrIpcorD4NHvFye+7Vrkv94Dc853zP93venl/E/36Keg60RsRgROyOiEpEPI+IB0VR/FyzKgYw48/zBv1rJe/HN7zDKNrzDmMqfc2JoAVvk3xjib8zfa/R0ozA4WzFaAPMWGKG8vskLuDIiqK4lMHtDTAdibmY3+9rZrSnGl+piV9YqcpY3jwREUVRdEXEhog4GhGtETGJznrZHchMhhtUcCIxh0p8u/ADV+sFV3KAU2VZYBNmE7OuDsdj3K+XYGAfvua2jGXPOzLz2VzT/Q3iH2GykUCByyU/2dK50iB2B77jWj3ATjxNos+4hfG8E2mDJ+irid2LaXzCljLyQcxjDmctvkW1mFacwwd8wUCV72GKH6+X+TxeYGvd/i3je/AqRfrSNo6F5DldDS6y5LnVkFfFbcPHHGqRtu24i184tQQcytLOrJa8SuR8xh6ssrXhTm5bd+BmDq+tCYH12aYbNfbe3KbrYfH9mPhb8iqy25gusd/Ds0pEbI6ImWYFImI6IrpK7C8jojcwgu5m2dGFYyX2How0y/vvnN8dpHfeBcHNQgAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Change sub-issue"},"description":"Add a sub-issue to a parent issue in a GitHub repository.","inputSchema":{"type":"object","properties":{"after_id":{"type":"number","description":"The ID of the sub-issue to be prioritized after (either after_id OR before_id should be specified)"},"before_id":{"type":"number","description":"The ID of the sub-issue to be prioritized before (either after_id OR before_id should be specified)"},"issue_number":{"type":"number","description":"The number of the parent issue"},"method":{"type":"string","description":"The action to perform on a single sub-issue\nOptions are:\n- 'add' - add a sub-issue to a parent issue in a GitHub repository.\n- 'remove' - remove a sub-issue from a parent issue in a GitHub repository.\n- 'reprioritize' - change the order of sub-issues within a parent issue in a GitHub repository. Use either 'after_id' or 'before_id' to specify the new position.\nWrites issue hierarchy. To move a sub-issue to a new parent, use `add` with `replace_parent=true`; there is no writable parent field.\n"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"replace_parent":{"type":"boolean","description":"When true, replaces the sub-issue's current parent issue. Use with 'add' method only."},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"sub_issue_id":{"type":"number","description":"The ID of the sub-issue to add. ID is not the same as issue number"}},"required":["method","owner","repo","issue_number","sub_issue_id"]},"name":"sub_issue_write","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Update issue comment"},"description":"Update the body of an existing issue or pull request conversation comment. This tool cannot update pull request review comments.","inputSchema":{"type":"object","properties":{"body":{"type":"string","description":"New comment content","minLength":1},"comment_id":{"type":"integer","description":"The numeric ID of the issue or pull request conversation comment to update. Do not use a pull request review comment ID.","minimum":1},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","comment_id","body"]},"name":"update_issue_comment","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC2UlEQVRIicWVMUyTaRjHf/+vVUpOJOdilOYoUvVreq2CDmLOwdlAS5xMbrrhBifjYG7QjYuJw108nZx1huLgYlw0QshxChUK2koxHHcuCmgsFfieG0or3kGPSoz/8X3f5/97nvfN8z7wmaVqm9FodFfR/EnMugy5giCAwYwgI9Ff9Hl9L9Lp1zUBgsGO+rqGwnlhF4CdwBSyAUwvS1G220zHBSFgHtmVxYWvfp2ZGSj8LyAcjgfZphTQZtAr7OdsZvSP9RIJR+LtQhcNusF+d1aUfPp05M8NAavmg8AOk3cmN56+s1Hpa7U/cviUYbcw3jgex9ZCKoBgsKM+0PDuAdBq2He5zOiTzZiXtc+NxRw598GeLRcWTuTz+UUAp3ygrqFwHmgzeWdqNQd4PpFOy/gedNQfaDz3UQXRaHRX0fNPGdzNZUZO12q+VuHIoV7g5Hu/1/IinX7tABTNnwR2SvRsxRzAzOsBGuuWfAkoX5FZFzCVHR95tFVAbiI9DEwj6wLwl5YVMTRYLTDsxjuRrgGYYz/kxkbvbXRWMOBB24cKYI9ks1X8hXQDaAaa5XG9WjImZgV71wI+m8qAv8y0t1pSmP0ITANTmHO2qqvRZDALlTdgHKyjWkx2YvQ2cHtTacMxYBhWK5DoF4TCkXj7Jg02VKsbOwI0Y+qvALZrOQXMC13cKkDSJWBuJUCqAhgbG3uF7IpBcn/k8KlPNQ+78U5QQuhy/vHjuQoAYPndwi9gw4bd2ufGYrWatxz8No50ExhaKsz9Vl6vAPL5/KKzoiTGG0fOg1oqCbvxTp/juw/M+zynu/yTwjoD58CBQ02ez/pAR4E+M69ntf3/o1Y3dqR050oAQz7P6Z6cfPRRw647MkOhUMAfaDyH+AloBKYNPRT292rQHoMOSp09J3TZlt5ezWazxX97VR3638RiX9ct+RImSyBcrDT0ETMyMp4ptRIgVX7QL6J/ALSUEwJ5rdg2AAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABxElEQVRIibWVvW4TURCFv0vlNEDcIHAkKAERJOIKSjoUArwBPwUFFaKIIngAJARCPIgdh4cgRBYt6ZIAEYEqdhpEw0fhCbkKa68dxyNtsfNzzpnZu3NhwpYGBdUqcA+4A1wEZiK0DawD74FWSml3JAJ1CngGLAIngU1gFfgZKWeAG8AFoAu8At6mlH6VtqTOqJ/UP2pDnRuQO6c27VlbrQ0Dvq121Fulag7q5qPmW18SdSqUd9Qrw4Jn9bNR21YrRQkvYixDKy/AuB3jWjocqKpdtXFU8AxrOTqZzp2PgvnaMRDUA+tB7mypG+OCZ3hbahPgRPguAR9LihaicEu9WcKxClzOi/fU1wPAk7rjgX0uEfNG3cs7mJjtE+wA5/olpZQEHgNf6K2NJyW4NeD7v7c4WptjSc0svlMjdzyM2fbdOyOA7x/T+7mzGj9H8xgIWuquevpw4HmsivkxwBdC/WJRsBKLqqPOHgH8aqybtcJlF0m1WLndUToJ5V31q9r3NOYk7Wh1Wa0PyK3HzA3l/4H3uzIrwFNgCThF7/x/AH5EylngOnAe6AAvgXcppd9DEWRE08DdeIou/RVgJaXUGYQzUfsL+zmwV7BtIq0AAAAASUVORK5CYII=","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Edit pull request"},"description":"Update an existing pull request in a GitHub repository.","inputSchema":{"type":"object","properties":{"base":{"type":"string","description":"New base branch name"},"body":{"type":"string","description":"New description"},"draft":{"type":"boolean","description":"Mark pull request as draft (true) or ready for review (false)"},"maintainer_can_modify":{"type":"boolean","description":"Allow maintainer edits"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number to update"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"},"reviewers":{"type":"array","items":{"type":"string"},"description":"GitHub usernames or ORG/team-slug team reviewers to request reviews from"},"state":{"type":"string","description":"New state","enum":["open","closed"]},"title":{"type":"string","description":"New title"}},"required":["owner","repo","pullNumber"]},"name":"update_pull_request","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]},{"annotations":{"idempotentHint":false,"readOnlyHint":false,"title":"Update pull request branch"},"description":"Update the branch of a pull request with the latest changes from the base branch.","inputSchema":{"type":"object","properties":{"expectedHeadSha":{"type":"string","description":"The expected SHA of the pull request's HEAD ref"},"owner":{"type":"string","description":"Repository owner","x-mcp-header":"owner"},"pullNumber":{"type":"number","description":"Pull request number"},"repo":{"type":"string","description":"Repository name","x-mcp-header":"repo"}},"required":["owner","repo","pullNumber"]},"name":"update_pull_request_branch","icons":[{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACwUlEQVRIie2Vz28UZRjHP993pi0QIC3YahNjirtmd3bS3Q1eUHvQEPUiEv8A4kXjwRJ78MCFBLjBBRKCHowHE38cNCbGGx6IUoKiodtNpoNmTJp4oSJNQ3pw29l5POxus2wo3QTwxPc0887zfD7zvu9kXnjEUfdNrjj5vJOmMP4e9JrfR1G02tuQD8tvgpck0dxCPwK30ViqnJTcr4bOmfRlI/PrhUJ5313woDpDpu8ss7f6nYHrvDnGcYlPsoY/bKaXwHY3HWfvgmNnMX0zvMM7069A3c3pkEYWa7UVgFxQPSfs7SSeH3k2rEy5jMubMBoG1yQ+SBbm53of+gCybMkk/H8VAFdbZisZLAFsJ11oyL+BUURcwrjWAZixXeIwxs/5UuVAr0QAYRjubGR+HWy3mb6QCIBXQe8nce0jgIkwfMo3/xLG085x8I9ofkMyUa0O+w2rgS0mcf3lboEDiKJo1cvsIDDr4D1DhTb8407hYhTdTJW+AvrdMnuhG9Je1m9BBzbfjXbyQcXypeqJLQt7+0rVE/mgYr3j7l7FDzOPBY8FDx6vc1EolPeNjI5/Jpgw7Lm9o+Pry//c/K0PhnLFyrSMDxE79jwxvn9079gvt28vrUD7V1EoFHalbltd2C7DfS4sAF4DTSdx7cL96LliZVriPPADuL+geRh0Z8il5SiKVn2ATENvCCYw78U/b8xdBcgHlYuGHQXuK5A4ClxM4vnXW8Lqp5JdWWt6h4CvWnsgxgDSbRZ3Gg0tCJ7sY4nGwDZOt/WBZtzN9FswLgM2sGann5mcPDaw5pWEHQH7cUu86SdkR3LF6tfrA814MNVpwNrM1leUxPXrSKfMeHcwdctyNotY8c3NbMX3LJsB3ZHsymDqlkHvYHYyievXWxPpSj4o75eYIuPWZof+vRKG4c61pncIx6gZsx34/5L/ACy3ElqUYhuvAAAAAElFTkSuQmCC","mimeType":"image/png","theme":"light"},{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABn0lEQVRIie2Vvy5EURDGvyMUkhUU/pS2Q2clwvIKqL0Cm9ArrBcQShJPoLFR2YaIhIjQ7a7E39IiCgqVn8KsPY697E3oTHPumTPf990z986M9Mfm/A0wKGlMUlnSlnPuOQQAE5LOnXOFWErAIvBK1S6BZBAzZ2fzcckHjXwVaAXSwD2wWYN8A2iKK1ABt3m+ZeDRnseIthdgDxioxd1o662tfZIO7Lnf8xcklST1StqRdORxNEualHQIDDvnTmvdIGE5vwdWgLy93bQX0w0UgSdgKMC3AdfA7ndpSgKbduUbYBoI/7Ju4BiYrYFfAl4iBbxAgOyPgV9xWYDQ3xCXKK79C/wL/KJZoeW8QpsJCy0C54AMULaGmQu7sIAW4MpaxTKwbQU3U4dAxmLzwLpxXAIJP2jKgkY8Xx4o1SFwBmx7+7RxTUnVb9Bpa9HDFiR1/SRgWH+6FT3/h2qK6sBpB0aBB7yB880NcpaWtGHXjCsVBmb5PDIvgJ46BJKW84q9AguV87Adp/Q+9O8UMfQjRBKSxiV1SNp3zp3Ug/sVewPruexhKwhGXQAAAABJRU5ErkJggg==","mimeType":"image/png","theme":"dark"}]}]}} | ||
| 3 | + | ||
modified
docs/README.md +2 -0 | @@ -8,3 +8,5 @@ Choose a language: | ||
| 8 | 8 | - [Français](fr/README.md) |
| 9 | 9 | |
| 10 | 10 | Both trees follow the [Diátaxis](https://diataxis.fr) layout: `tutorials/` to learn, `how-to/` to get a task done, `reference/` to look something up, `explanation/` to understand. |
| 11 | + | |
| 12 | +Both include **MCP (Model Context Protocol)** documentation integrated in their respective quadrants. | |
| @@ -8,3 +8,5 @@ Choose a language: | |||
| 8 | - [Français](fr/README.md) | 8 | - [Français](fr/README.md) |
| 9 | 9 | ||
| 10 | Both trees follow the [Diátaxis](https://diataxis.fr) layout: `tutorials/` to learn, `how-to/` to get a task done, `reference/` to look something up, `explanation/` to understand. | 10 | Both trees follow the [Diátaxis](https://diataxis.fr) layout: `tutorials/` to learn, `how-to/` to get a task done, `reference/` to look something up, `explanation/` to understand. |
| 11 | + | ||
| 12 | +Both include **MCP (Model Context Protocol)** documentation integrated in their respective quadrants. | ||
modified
docs/en/README.md +14 -0 | @@ -23,6 +23,11 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | ||
| 23 | 23 | - [Navigate and refactor code with a language server (`lsp`)](how-to/navigate-code-with-lsp.md) |
| 24 | 24 | - [Run a single prompt and exit (`-p`/`--prompt`)](how-to/run-a-single-prompt.md) |
| 25 | 25 | |
| 26 | +### MCP (Model Context Protocol) How-to | |
| 27 | +- [Use Context7 for documentation](how-to/mcp-use-context7.md) | |
| 28 | +- [Use Wikipedia for research](how-to/mcp-use-wikipedia.md) | |
| 29 | +- [Use GitHub for code access](how-to/mcp-use-github.md) | |
| 30 | + | |
| 26 | 31 | ## Reference — look it up |
| 27 | 32 | |
| 28 | 33 | - [Command line](reference/cli.md) — flags, environment variables, start-up banner |
| @@ -32,6 +37,11 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | ||
| 32 | 37 | - [Skill file format](reference/skill-format.md) |
| 33 | 38 | - [Agent Client Protocol surface](reference/acp.md) |
| 34 | 39 | |
| 40 | +### MCP (Model Context Protocol) Reference | |
| 41 | +- [MCP Configuration](reference/mcp-configuration.md) — All MCP-specific configuration options | |
| 42 | +- [Available Tools](reference/mcp-available-tools.md) — Complete list of tools per MCP server | |
| 43 | +- [Authentication](reference/mcp-authentication.md) — Supported authentication methods for MCP servers | |
| 44 | + | |
| 35 | 45 | ## Explanation — understand |
| 36 | 46 | |
| 37 | 47 | - [Architecture: one loop, three front ends](explanation/architecture.md) |
| @@ -42,6 +52,10 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | ||
| 42 | 52 | - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive |
| 43 | 53 | - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
| 44 | 54 | |
| 55 | +### MCP (Model Context Protocol) Explanation | |
| 56 | +- [MCP Architecture](explanation/mcp-architecture.md) — How MCP is integrated into mini-me | |
| 57 | +- [Design Decisions](explanation/mcp-decisions.md) — Why these technical choices for MCP? | |
| 58 | + | |
| 45 | 59 | ## Scope |
| 46 | 60 | |
| 47 | 61 | This documentation covers the `mm` binary and the packages under `internal/`. The `kits/` and `tmp/` directories at the repository root are not part of the project and are not documented here. |
| @@ -23,6 +23,11 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | |||
| 23 | - [Navigate and refactor code with a language server (`lsp`)](how-to/navigate-code-with-lsp.md) | 23 | - [Navigate and refactor code with a language server (`lsp`)](how-to/navigate-code-with-lsp.md) |
| 24 | - [Run a single prompt and exit (`-p`/`--prompt`)](how-to/run-a-single-prompt.md) | 24 | - [Run a single prompt and exit (`-p`/`--prompt`)](how-to/run-a-single-prompt.md) |
| 25 | 25 | ||
| 26 | +### MCP (Model Context Protocol) How-to | ||
| 27 | +- [Use Context7 for documentation](how-to/mcp-use-context7.md) | ||
| 28 | +- [Use Wikipedia for research](how-to/mcp-use-wikipedia.md) | ||
| 29 | +- [Use GitHub for code access](how-to/mcp-use-github.md) | ||
| 30 | + | ||
| 26 | ## Reference — look it up | 31 | ## Reference — look it up |
| 27 | 32 | ||
| 28 | - [Command line](reference/cli.md) — flags, environment variables, start-up banner | 33 | - [Command line](reference/cli.md) — flags, environment variables, start-up banner |
| @@ -32,6 +37,11 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | |||
| 32 | - [Skill file format](reference/skill-format.md) | 37 | - [Skill file format](reference/skill-format.md) |
| 33 | - [Agent Client Protocol surface](reference/acp.md) | 38 | - [Agent Client Protocol surface](reference/acp.md) |
| 34 | 39 | ||
| 40 | +### MCP (Model Context Protocol) Reference | ||
| 41 | +- [MCP Configuration](reference/mcp-configuration.md) — All MCP-specific configuration options | ||
| 42 | +- [Available Tools](reference/mcp-available-tools.md) — Complete list of tools per MCP server | ||
| 43 | +- [Authentication](reference/mcp-authentication.md) — Supported authentication methods for MCP servers | ||
| 44 | + | ||
| 35 | ## Explanation — understand | 45 | ## Explanation — understand |
| 36 | 46 | ||
| 37 | - [Architecture: one loop, three front ends](explanation/architecture.md) | 47 | - [Architecture: one loop, three front ends](explanation/architecture.md) |
| @@ -42,6 +52,10 @@ This documentation is organised along the four [Diátaxis](https://diataxis.fr) | |||
| 42 | - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive | 52 | - [Thinking models](explanation/thinking-models.md) — where a model's reasoning comes from, why `mm` reads it below Genkit, and why reading it keeps the watchdog alive |
| 43 | - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) | 53 | - Package diagram (draw.io source): [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
| 44 | 54 | ||
| 55 | +### MCP (Model Context Protocol) Explanation | ||
| 56 | +- [MCP Architecture](explanation/mcp-architecture.md) — How MCP is integrated into mini-me | ||
| 57 | +- [Design Decisions](explanation/mcp-decisions.md) — Why these technical choices for MCP? | ||
| 58 | + | ||
| 45 | ## Scope | 59 | ## Scope |
| 46 | 60 | ||
| 47 | This documentation covers the `mm` binary and the packages under `internal/`. The `kits/` and `tmp/` directories at the repository root are not part of the project and are not documented here. | 61 | This documentation covers the `mm` binary and the packages under `internal/`. The `kits/` and `tmp/` directories at the repository root are not part of the project and are not documented here. |
added
docs/en/explanation/mcp-architecture.md +66 -0 | new file mode 100644 | ||
| @@ -0,0 +1,66 @@ | ||
| 1 | +# Architecture: one loop, three front ends — explanation | |
| 2 | + | |
| 3 | +## What is this about? | |
| 4 | + | |
| 5 | +`mm` is deliberately small: an agent loop, a handful of tools, and a configuration file. What makes it worth looking at is how the pieces are cut so that the same loop can be driven from a terminal — line by line or in a window — and from an editor without duplicating anything. | |
| 6 | + | |
| 7 | +The entry point, `main.go`, only does wiring: it loads the settings, opens the engine, builds the tool list, prints the banner, and hands everything to one of three front ends. Each concern lives in its own package under `internal/`: | |
| 8 | + | |
| 9 | +| Package | Responsibility | | |
| 10 | +|---------|----------------| | |
| 11 | +| `config` | Every setting in one struct, with built-in defaults overridden by YAML and a few environment variables. | | |
| 12 | +| `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, the provider registry, and the reasoning a thinking model streams beside its answer (see [Thinking models](thinking-models.md)). | | |
| 13 | +| `tools` | The built-in tools the model can call. | | |
| 14 | +| `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. | | |
| 15 | +| `lsp` | The language-server client behind the `lsp` tool: starts the configured servers on first use (config `lsp.servers`), syncs files from disk, asks for definition, references, hover, symbols, diagnostics and rename, and shapes the answers as `path:line:column <line>` text. Built on `go.lsp.dev/protocol` and `go.lsp.dev/jsonrpc2`. | | |
| 16 | +| `skills` | Discovery of markdown procedures and rendering of the `read_skill` catalogue. | | |
| 17 | +| `command` | The one table of slash commands: name, description, which front ends offer it, whether it takes an argument. The REPL dispatches on it, the ACP façade advertises its Editor slice, the window's `/` picker lists it. | | |
| 18 | +| `clipboard` | Puts text on the system clipboard: a native tool on `PATH` first (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 as the fallback, and it reports which. Used by `/copy` in the REPL and the window. | | |
| 19 | +| `store` | Per-project session files (`<dir>/<id>.json`, whole rewrite, atomic): create, save, load, list, latest. Used by both front ends when `sessions.enabled`. | | |
| 20 | +| `mention` | The `@path` notation of a question: existing paths become `[attached file: …]` lines, the same shape as an editor's `resource_link`. Used by both front ends. | | |
| 21 | +| `detector` | Detection of the same action repeated with the same result. | | |
| 22 | +| `compact` | Compression of the history into a model-written summary. | | |
| 23 | +| `spinner` | The "still working" indicator on a terminal. | | |
| 24 | +| `version` | Which `mm` this is: the tag stamped at build time, or the commit Go embeds otherwise. Read by `--version`, by the start-up banner and by the window's status bar. | | |
| 25 | +| `ui` | Where human-facing output goes, and the event sink the front ends share. | | |
| 26 | +| `agent` | The terminal REPL. | | |
| 27 | +| `acp` | The Agent Client Protocol façade. | | |
| 28 | +| `tui` | The single-window front end (`mm -tui`): a `ui.Sink` drawing on tcell, one transcript value, an event loop woken by the engine's and tools' goroutines. | | |
| 29 | + | |
| 30 | +A draw.io diagram of these packages and their dependencies is kept at [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio). | |
| 31 | + | |
| 32 | +## Why it is designed this way | |
| 33 | + | |
| 34 | +**Tools report failures as text, never as errors.** A command that exits non-zero, a file that does not exist, an edit that is ambiguous: all of it goes back to the model as the tool's output. The model is the one that must react, so it is the one that must read the message. A Go error would end the turn instead. | |
| 35 | + | |
| 36 | +**The full history is kept even when a turn fails.** Genkit only returns the last message; the engine returns the whole conversation including tool calls and results. The front ends keep that history after an abort, a watchdog cut or a `maxTurns` overrun, because the commands that already ran are facts the model must not re-run or invent on the next question. Only a question that got no answer at all is dropped. | |
| 37 | + | |
| 38 | +**Output is shown before the model sees it.** In the terminal, `bash` echoes the first lines of a command's output before returning it. "Show me that file" means show it; leaving it to the model to summarise was observed to leave the user with nothing. | |
| 39 | + | |
| 40 | +**One loop, three front ends.** The terminal REPL, the window and the ACP façade receive exactly the same engine, system prompt and tool list. What differs is where events go. In the terminal, the tools print their own `🛠️` lines and nobody asks permission. In ACP mode, stdout belongs to JSON-RPC, so `main.go` redirects the human output to stderr, disables the spinner, and the façade installs a `ui.Sink` for each turn. The tools check for an active sink: with one, they emit `ToolStart`, `Allow`, `ToolRunning`, `ToolEnd` events instead of printing. The `ui` package knows nothing about the protocol; `acp` is the only place that translates those events into `session/update` notifications and permission requests. The window mode is the same trick a second time: `tui` installs its own `ui.Sink` for each turn, and the events become entries of a transcript drawn on the screen — so the tools needed no change to gain a third home, and the inline permission prompt of `permissions: ask` is the terminal's version of the editor's dialog. | |
| 41 | + | |
| 42 | +**One command table.** The slash commands live in `internal/command`, marked with the front ends that offer them. The REPL and the window read the same table; the ACP façade advertises only the Editor slice (`/new`, `/compact`) through `available_commands_update`, because the editor owns the process and cancels turns itself. A command added in one place is therefore offered — or deliberately not — everywhere at once. | |
| 43 | + | |
| 44 | +**The window draws from a value, on one goroutine.** The engine streams chunks and the tools emit events from their own goroutines; the window records them into a `Transcript` (a mutex-guarded value) and posts a wake-up event. Only the event loop draws, and it redraws from the transcript's current state every frame, so a dropped wake-up costs a late frame and never a lost one. This is the rule turbo-core's agent window follows, and the reason the window needs no widget library: a painter, a transcript, a layout function and a key map are enough. | |
| 45 | + | |
| 46 | +**Permission lives in the sink, not in the tools.** A permission dialog is the main thing an editor adds and the terminal does not have. Putting the policy in the ACP sink keeps the tools identical in both modes; "allow always" is remembered per tool name for the session. | |
| 47 | + | |
| 48 | +**Turns are serialised in ACP mode.** The active sink is process-global and the engine was never built for concurrent generations, so a second `session/prompt` waits for the first to finish. The protocol leaves that choice to the agent, and editors do not send overlapping prompts anyway. | |
| 49 | + | |
| 50 | +**The loop detector is separate from the history.** It records tool, input and output triples; the same triple three times in a row makes the tool append an instruction to change approach. It is deliberately not reset by `/compact`: forgetting a conversation does not make a repeated command new. Advice is not enough for every model: on 2026-09-18 a 12B model repeated the same refused `edit_file` call thirty times, `[LOOP_DETECTED]` appended to each answer, until the user pressed Escape. So the detector has a second level: three flagged repeats after the threshold — six identical calls — and the tool returns a Go error instead of text, the one deliberate exception to "tools return text": the agent is stopping itself, not reporting a failure for the model to read. The engine shows the detector's sentence as the turn's error, does not retry the turn, and the session file records it. A third guard sits in the stream itself: the arguments of a tool call arrive delta by delta and none of it is shown, so a model that derails while composing one — `"Greet 2023-06-15 11:16:00 +0000 UTC 2023-06-15 …"` for four minutes, seen the same day — looks like nothing happening. The engine watches those deltas and stops the generation as soon as they repeat a pattern or pass a quarter megabyte. | |
| 51 | + | |
| 52 | +## Rejected alternatives | |
| 53 | + | |
| 54 | +- **One binary per front end.** Rejected because the point of the exercise is to prove the loop is the same; sharing the wiring in `main.go` is what makes that visible. | |
| 55 | +- **Printing directly from the ACP façade.** Impossible: the specification forbids anything but protocol messages on stdout. | |
| 56 | +- **Returning Go errors from tools.** Rejected because it ends the turn instead of letting the model recover. | |
| 57 | + | |
| 58 | +## How it relates to the rest | |
| 59 | + | |
| 60 | +- The provider registry that the engine uses is described in [providers](providers.md). | |
| 61 | +- Why and how the history is shortened is in [context compression](context-compression.md). | |
| 62 | +- The exact protocol surface is in the [ACP reference](../reference/acp.md); the tools' contract is in the [tools reference](../reference/tools.md). | |
| 63 | + | |
| 64 | +## Package map | |
| 65 | + | |
| 66 | +The import graph of every package, with a one-line purpose on each, is kept as a draw.io diagram at [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio) (opens in diagrams.net or the VS Code Draw.io extension). It is regenerated from `go list` whenever a package is added, removed or re-wired. | |
| new file mode 100644 | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | +# Architecture: one loop, three front ends — explanation | ||
| 2 | + | ||
| 3 | +## What is this about? | ||
| 4 | + | ||
| 5 | +`mm` is deliberately small: an agent loop, a handful of tools, and a configuration file. What makes it worth looking at is how the pieces are cut so that the same loop can be driven from a terminal — line by line or in a window — and from an editor without duplicating anything. | ||
| 6 | + | ||
| 7 | +The entry point, `main.go`, only does wiring: it loads the settings, opens the engine, builds the tool list, prints the banner, and hands everything to one of three front ends. Each concern lives in its own package under `internal/`: | ||
| 8 | + | ||
| 9 | +| Package | Responsibility | | ||
| 10 | +|---------|----------------| | ||
| 11 | +| `config` | Every setting in one struct, with built-in defaults overridden by YAML and a few environment variables. | | ||
| 12 | +| `engine` | The connection to the model server and the generation itself: streaming, watchdog, non-streaming retry, summary requests, the provider registry, and the reasoning a thinking model streams beside its answer (see [Thinking models](thinking-models.md)). | | ||
| 13 | +| `tools` | The built-in tools the model can call. | | ||
| 14 | +| `fileedit` | Exact-replacement file editing, the rules behind `edit_file`. | | ||
| 15 | +| `lsp` | The language-server client behind the `lsp` tool: starts the configured servers on first use (config `lsp.servers`), syncs files from disk, asks for definition, references, hover, symbols, diagnostics and rename, and shapes the answers as `path:line:column <line>` text. Built on `go.lsp.dev/protocol` and `go.lsp.dev/jsonrpc2`. | | ||
| 16 | +| `skills` | Discovery of markdown procedures and rendering of the `read_skill` catalogue. | | ||
| 17 | +| `command` | The one table of slash commands: name, description, which front ends offer it, whether it takes an argument. The REPL dispatches on it, the ACP façade advertises its Editor slice, the window's `/` picker lists it. | | ||
| 18 | +| `clipboard` | Puts text on the system clipboard: a native tool on `PATH` first (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 as the fallback, and it reports which. Used by `/copy` in the REPL and the window. | | ||
| 19 | +| `store` | Per-project session files (`<dir>/<id>.json`, whole rewrite, atomic): create, save, load, list, latest. Used by both front ends when `sessions.enabled`. | | ||
| 20 | +| `mention` | The `@path` notation of a question: existing paths become `[attached file: …]` lines, the same shape as an editor's `resource_link`. Used by both front ends. | | ||
| 21 | +| `detector` | Detection of the same action repeated with the same result. | | ||
| 22 | +| `compact` | Compression of the history into a model-written summary. | | ||
| 23 | +| `spinner` | The "still working" indicator on a terminal. | | ||
| 24 | +| `version` | Which `mm` this is: the tag stamped at build time, or the commit Go embeds otherwise. Read by `--version`, by the start-up banner and by the window's status bar. | | ||
| 25 | +| `ui` | Where human-facing output goes, and the event sink the front ends share. | | ||
| 26 | +| `agent` | The terminal REPL. | | ||
| 27 | +| `acp` | The Agent Client Protocol façade. | | ||
| 28 | +| `tui` | The single-window front end (`mm -tui`): a `ui.Sink` drawing on tcell, one transcript value, an event loop woken by the engine's and tools' goroutines. | | ||
| 29 | + | ||
| 30 | +A draw.io diagram of these packages and their dependencies is kept at [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio). | ||
| 31 | + | ||
| 32 | +## Why it is designed this way | ||
| 33 | + | ||
| 34 | +**Tools report failures as text, never as errors.** A command that exits non-zero, a file that does not exist, an edit that is ambiguous: all of it goes back to the model as the tool's output. The model is the one that must react, so it is the one that must read the message. A Go error would end the turn instead. | ||
| 35 | + | ||
| 36 | +**The full history is kept even when a turn fails.** Genkit only returns the last message; the engine returns the whole conversation including tool calls and results. The front ends keep that history after an abort, a watchdog cut or a `maxTurns` overrun, because the commands that already ran are facts the model must not re-run or invent on the next question. Only a question that got no answer at all is dropped. | ||
| 37 | + | ||
| 38 | +**Output is shown before the model sees it.** In the terminal, `bash` echoes the first lines of a command's output before returning it. "Show me that file" means show it; leaving it to the model to summarise was observed to leave the user with nothing. | ||
| 39 | + | ||
| 40 | +**One loop, three front ends.** The terminal REPL, the window and the ACP façade receive exactly the same engine, system prompt and tool list. What differs is where events go. In the terminal, the tools print their own `🛠️` lines and nobody asks permission. In ACP mode, stdout belongs to JSON-RPC, so `main.go` redirects the human output to stderr, disables the spinner, and the façade installs a `ui.Sink` for each turn. The tools check for an active sink: with one, they emit `ToolStart`, `Allow`, `ToolRunning`, `ToolEnd` events instead of printing. The `ui` package knows nothing about the protocol; `acp` is the only place that translates those events into `session/update` notifications and permission requests. The window mode is the same trick a second time: `tui` installs its own `ui.Sink` for each turn, and the events become entries of a transcript drawn on the screen — so the tools needed no change to gain a third home, and the inline permission prompt of `permissions: ask` is the terminal's version of the editor's dialog. | ||
| 41 | + | ||
| 42 | +**One command table.** The slash commands live in `internal/command`, marked with the front ends that offer them. The REPL and the window read the same table; the ACP façade advertises only the Editor slice (`/new`, `/compact`) through `available_commands_update`, because the editor owns the process and cancels turns itself. A command added in one place is therefore offered — or deliberately not — everywhere at once. | ||
| 43 | + | ||
| 44 | +**The window draws from a value, on one goroutine.** The engine streams chunks and the tools emit events from their own goroutines; the window records them into a `Transcript` (a mutex-guarded value) and posts a wake-up event. Only the event loop draws, and it redraws from the transcript's current state every frame, so a dropped wake-up costs a late frame and never a lost one. This is the rule turbo-core's agent window follows, and the reason the window needs no widget library: a painter, a transcript, a layout function and a key map are enough. | ||
| 45 | + | ||
| 46 | +**Permission lives in the sink, not in the tools.** A permission dialog is the main thing an editor adds and the terminal does not have. Putting the policy in the ACP sink keeps the tools identical in both modes; "allow always" is remembered per tool name for the session. | ||
| 47 | + | ||
| 48 | +**Turns are serialised in ACP mode.** The active sink is process-global and the engine was never built for concurrent generations, so a second `session/prompt` waits for the first to finish. The protocol leaves that choice to the agent, and editors do not send overlapping prompts anyway. | ||
| 49 | + | ||
| 50 | +**The loop detector is separate from the history.** It records tool, input and output triples; the same triple three times in a row makes the tool append an instruction to change approach. It is deliberately not reset by `/compact`: forgetting a conversation does not make a repeated command new. Advice is not enough for every model: on 2026-09-18 a 12B model repeated the same refused `edit_file` call thirty times, `[LOOP_DETECTED]` appended to each answer, until the user pressed Escape. So the detector has a second level: three flagged repeats after the threshold — six identical calls — and the tool returns a Go error instead of text, the one deliberate exception to "tools return text": the agent is stopping itself, not reporting a failure for the model to read. The engine shows the detector's sentence as the turn's error, does not retry the turn, and the session file records it. A third guard sits in the stream itself: the arguments of a tool call arrive delta by delta and none of it is shown, so a model that derails while composing one — `"Greet 2023-06-15 11:16:00 +0000 UTC 2023-06-15 …"` for four minutes, seen the same day — looks like nothing happening. The engine watches those deltas and stops the generation as soon as they repeat a pattern or pass a quarter megabyte. | ||
| 51 | + | ||
| 52 | +## Rejected alternatives | ||
| 53 | + | ||
| 54 | +- **One binary per front end.** Rejected because the point of the exercise is to prove the loop is the same; sharing the wiring in `main.go` is what makes that visible. | ||
| 55 | +- **Printing directly from the ACP façade.** Impossible: the specification forbids anything but protocol messages on stdout. | ||
| 56 | +- **Returning Go errors from tools.** Rejected because it ends the turn instead of letting the model recover. | ||
| 57 | + | ||
| 58 | +## How it relates to the rest | ||
| 59 | + | ||
| 60 | +- The provider registry that the engine uses is described in [providers](providers.md). | ||
| 61 | +- Why and how the history is shortened is in [context compression](context-compression.md). | ||
| 62 | +- The exact protocol surface is in the [ACP reference](../reference/acp.md); the tools' contract is in the [tools reference](../reference/tools.md). | ||
| 63 | + | ||
| 64 | +## Package map | ||
| 65 | + | ||
| 66 | +The import graph of every package, with a one-line purpose on each, is kept as a draw.io diagram at [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio) (opens in diagrams.net or the VS Code Draw.io extension). It is regenerated from `go list` whenever a package is added, removed or re-wired. | ||
added
docs/en/explanation/mcp-decisions.md +384 -0 | new file mode 100644 | ||
| @@ -0,0 +1,384 @@ | ||
| 1 | +# MCP Design Decisions | |
| 2 | + | |
| 3 | +This document explains the key design decisions made when implementing MCP (Model Context Protocol) support in mini-me. | |
| 4 | + | |
| 5 | +## Table of Contents | |
| 6 | + | |
| 7 | +- [HTTP-Only Transport](#http-only-transport) | |
| 8 | +- [Text-Only Support](#text-only-support) | |
| 9 | +- [No Stdio Transport (Yet)](#no-stdio-transport-yet) | |
| 10 | +- [No OAuth 2.0 Support](#no-oauth-20-support) | |
| 11 | +- [No Caching](#no-caching) | |
| 12 | +- [No Lazy Discovery](#no-lazy-discovery) | |
| 13 | +- [Tool Name Prefixing](#tool-name-prefixing) | |
| 14 | +- [Tool Filtering](#tool-filtering) | |
| 15 | +- [Authentication via Environment Variables](#authentication-via-environment-variables) | |
| 16 | +- [Permission System](#permission-system) | |
| 17 | +- [Error Handling](#error-handling) | |
| 18 | +- [SSE Parsing](#sse-parsing) | |
| 19 | + | |
| 20 | +--- | |
| 21 | + | |
| 22 | +## HTTP-Only Transport | |
| 23 | + | |
| 24 | +**Decision:** Only implement HTTP transport (streamable HTTP), not stdio. | |
| 25 | + | |
| 26 | +**Rationale:** | |
| 27 | + | |
| 28 | +1. **Simplicity:** HTTP transport is simpler to implement and debug | |
| 29 | +2. **Common Use Case:** Most hosted MCP servers use HTTP transport | |
| 30 | +3. **Security:** HTTP over TLS provides built-in security | |
| 31 | +4. **Compatibility:** Works with all hosted MCP services (GitHub, Context7, Wikipedia, etc.) | |
| 32 | +5. **Implementation Effort:** Stdio requires subprocess management, which adds complexity | |
| 33 | + | |
| 34 | +**Trade-offs:** | |
| 35 | + | |
| 36 | +- Cannot use local stdio-based MCP servers | |
| 37 | +- Cannot use Docker-based MCP servers that only expose stdio | |
| 38 | + | |
| 39 | +**Future:** Stdio transport will be added when there's demand for local MCP servers. | |
| 40 | + | |
| 41 | +--- | |
| 42 | + | |
| 43 | +## Text-Only Support | |
| 44 | + | |
| 45 | +**Decision:** Only support MCP tools that use text input/output. Filter out binary tools. | |
| 46 | + | |
| 47 | +**Rationale:** | |
| 48 | + | |
| 49 | +1. **Design Philosophy:** Mini-me is designed as a text-based agent | |
| 50 | +2. **Safety:** Binary data can be dangerous (images, executables, etc.) | |
| 51 | +3. **Simplicity:** Text is easier to handle, log, and display | |
| 52 | +4. **Compatibility:** All major MCP servers (GitHub, Context7, Wikipedia) use text-only tools | |
| 53 | +5. **Context:** Mini-me's context window is text-based | |
| 54 | + | |
| 55 | +**Implementation:** | |
| 56 | + | |
| 57 | +```go | |
| 58 | +// In ConnectAll() after discovering tools | |
| 59 | +for _, tool := range tools { | |
| 60 | + if !tool.IsTextOnly() { | |
| 61 | + warnings = append(warnings, | |
| 62 | + fmt.Sprintf("mcp server %s: tool %s uses binary I/O - skipped (text-only)", | |
| 63 | + server.Name, tool.Name)) | |
| 64 | + continue | |
| 65 | + } | |
| 66 | + textTools = append(textTools, tool) | |
| 67 | +} | |
| 68 | +``` | |
| 69 | + | |
| 70 | +**Detection:** The `IsTextOnly()` method checks for: | |
| 71 | +- Input parameters with `type: "string"` and `format: "binary"` | |
| 72 | +- Input parameters with `type: "binary"` | |
| 73 | +- Output properties with `type: "string"` and `format: "binary"` | |
| 74 | +- Output properties with `type: "binary"` | |
| 75 | + | |
| 76 | +--- | |
| 77 | + | |
| 78 | +## No Stdio Transport (Yet) | |
| 79 | + | |
| 80 | +**Decision:** Defer stdio transport implementation. | |
| 81 | + | |
| 82 | +**Rationale:** | |
| 83 | + | |
| 84 | +1. **HTTP is Sufficient:** All currently known public MCP servers use HTTP | |
| 85 | +2. **Complexity:** Stdio requires subprocess management, input/output streaming | |
| 86 | +3. **Priorities:** Focus on HTTP transport first, add stdio later if needed | |
| 87 | +4. **Use Cases:** Stdio is mainly for local/self-hosted MCP servers | |
| 88 | + | |
| 89 | +**Workaround:** Use HTTP-based MCP servers or run MCP servers as HTTP services. | |
| 90 | + | |
| 91 | +**Future:** Will implement when users request local stdio-based MCP servers. | |
| 92 | + | |
| 93 | +--- | |
| 94 | + | |
| 95 | +## No OAuth 2.0 Support | |
| 96 | + | |
| 97 | +**Decision:** Only support bearer token and API key authentication. No OAuth 2.0 flow. | |
| 98 | + | |
| 99 | +**Rationale:** | |
| 100 | + | |
| 101 | +1. **Complexity:** OAuth 2.0 requires browser interaction, token refresh, etc. | |
| 102 | +2. **Headless:** Mini-me often runs in headless environments (CI/CD, scripts) | |
| 103 | +3. **Current Servers:** Major MCP servers (GitHub, Context7) support bearer tokens | |
| 104 | +4. **Security:** Bearer tokens are sufficient for most use cases | |
| 105 | +5. **Implementation Effort:** OAuth 2.0 flow is complex to implement correctly | |
| 106 | + | |
| 107 | +**Trade-offs:** | |
| 108 | + | |
| 109 | +- Cannot use MCP servers that require OAuth 2.0 | |
| 110 | +- GitHub MCP server works with PAT (Personal Access Token), not OAuth | |
| 111 | + | |
| 112 | +**Workaround:** Use bearer tokens (PAT) instead of OAuth for GitHub. | |
| 113 | + | |
| 114 | +**Future:** OAuth 2.0 will be added if a major MCP server requires it. | |
| 115 | + | |
| 116 | +--- | |
| 117 | + | |
| 118 | +## No Caching | |
| 119 | + | |
| 120 | +**Decision:** Don't cache discovered tools between runs. | |
| 121 | + | |
| 122 | +**Rationale:** | |
| 123 | + | |
| 124 | +1. **Freshness:** MCP servers may update their tools, caching could cause stale data | |
| 125 | +2. **Simplicity:** No need to manage cache invalidation, storage, etc. | |
| 126 | +3. **Startup Time:** Discovery is fast enough for most use cases | |
| 127 | +4. **Memory:** Caching would use more memory for minimal benefit | |
| 128 | +5. **Correctness:** Always have the latest tool list | |
| 129 | + | |
| 130 | +**Trade-offs:** | |
| 131 | + | |
| 132 | +- Slightly longer startup time (but discovery is parallel) | |
| 133 | +- Cannot use mini-me if MCP servers are offline at startup | |
| 134 | + | |
| 135 | +**Future:** May add optional caching if startup time becomes an issue. | |
| 136 | + | |
| 137 | +--- | |
| 138 | + | |
| 139 | +## No Lazy Discovery | |
| 140 | + | |
| 141 | +**Decision:** Discover all MCP tools at startup, not lazily. | |
| 142 | + | |
| 143 | +**Rationale:** | |
| 144 | + | |
| 145 | +1. **User Experience:** Users see all available tools immediately | |
| 146 | +2. **Error Detection:** Can detect configuration errors at startup | |
| 147 | +3. **Simplicity:** Easier to implement and reason about | |
| 148 | +4. **Consistency:** All tools available from first use | |
| 149 | +5. **Validation:** Can validate tool schemas at startup | |
| 150 | + | |
| 151 | +**Trade-offs:** | |
| 152 | + | |
| 153 | +- Longer startup time (mitigated by parallel discovery) | |
| 154 | +- All MCP servers must be accessible at startup | |
| 155 | + | |
| 156 | +**Future:** May add lazy discovery as an option for slow MCP servers. | |
| 157 | + | |
| 158 | +--- | |
| 159 | + | |
| 160 | +## Tool Name Prefixing | |
| 161 | + | |
| 162 | +**Decision:** Prefix all MCP tool names with their server name. | |
| 163 | + | |
| 164 | +**Rationale:** | |
| 165 | + | |
| 166 | +1. **Avoid Collisions:** Different MCP servers might have tools with the same name | |
| 167 | +2. **Clarity:** Users can see which server a tool comes from | |
| 168 | +3. **Namespacing:** Creates a natural namespace for MCP tools | |
| 169 | +4. **Discoverability:** Makes it clear where tools come from | |
| 170 | + | |
| 171 | +**Implementation:** | |
| 172 | + | |
| 173 | +```go | |
| 174 | +func SanitizeToolName(serverName, toolName string) (string, error) { | |
| 175 | + sanitized := sanitizeName(toolName) | |
| 176 | + fullName := serverName + "_" + sanitized | |
| 177 | + // Check for collision with built-in tools | |
| 178 | + if IsBuiltinTool(fullName) { | |
| 179 | + return "", fmt.Errorf("tool name %q collides with built-in tool", fullName) | |
| 180 | + } | |
| 181 | + return fullName, nil | |
| 182 | +} | |
| 183 | +``` | |
| 184 | + | |
| 185 | +**Examples:** | |
| 186 | +- Server: `github`, Tool: `list_repositories` → `github_list_repositories` | |
| 187 | +- Server: `wikipedia`, Tool: `search_wikipedia` → `wikipedia_search_wikipedia` | |
| 188 | +- Server: `context7`, Tool: `resolve-library-id` → `context7_resolve_library_id` | |
| 189 | + | |
| 190 | +**Name Sanitization:** | |
| 191 | +- Non-alphanumeric characters → `_` | |
| 192 | +- Leading/trailing `_` removed | |
| 193 | +- Multiple consecutive `_` collapsed to one | |
| 194 | + | |
| 195 | +--- | |
| 196 | + | |
| 197 | +## Tool Filtering | |
| 198 | + | |
| 199 | +**Decision:** Allow users to filter which tools from each MCP server are exposed. | |
| 200 | + | |
| 201 | +**Rationale:** | |
| 202 | + | |
| 203 | +1. **Focus:** Users may only need a subset of available tools | |
| 204 | +2. **Safety:** Prevent accidental use of powerful tools | |
| 205 | +3. **Performance:** Reduces tool list size, speeds up startup | |
| 206 | +4. **Customization:** Users can customize their tool set | |
| 207 | + | |
| 208 | +**Implementation:** | |
| 209 | + | |
| 210 | +```yaml | |
| 211 | +servers: | |
| 212 | + - name: github | |
| 213 | + transport: http | |
| 214 | + url: https://api.githubcopilot.com/mcp/ | |
| 215 | + auth: | |
| 216 | + type: bearer | |
| 217 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 218 | + tools: # Only these tools will be available | |
| 219 | + - get_me | |
| 220 | + - list_repositories | |
| 221 | + - search_code | |
| 222 | +``` | |
| 223 | + | |
| 224 | +**Default:** If `tools` list is empty or not specified, all text-only tools are exposed. | |
| 225 | + | |
| 226 | +--- | |
| 227 | + | |
| 228 | +## Authentication via Environment Variables | |
| 229 | + | |
| 230 | +**Decision:** All authentication tokens/keys are loaded from environment variables. | |
| 231 | + | |
| 232 | +**Rationale:** | |
| 233 | + | |
| 234 | +1. **Security:** Prevents accidental commit of secrets to version control | |
| 235 | +2. **Best Practice:** Follows the 12-factor app methodology | |
| 236 | +3. **Flexibility:** Allows different tokens for different environments | |
| 237 | +4. **Simplicity:** Easy to use with existing secret management | |
| 238 | +5. **Compatibility:** Works with Docker, Kubernetes, CI/CD systems | |
| 239 | + | |
| 240 | +**Implementation:** | |
| 241 | + | |
| 242 | +```go | |
| 243 | +func (c *HTTPClient) addAuthHeaders(req *http.Request) error { | |
| 244 | + switch c.server.Auth.Type { | |
| 245 | + case "none", "": | |
| 246 | + return nil | |
| 247 | + case "bearer": | |
| 248 | + token := os.Getenv(c.server.Auth.TokenEnv) | |
| 249 | + if token == "" { | |
| 250 | + return fmt.Errorf("missing bearer token for env var %s", c.server.Auth.TokenEnv) | |
| 251 | + } | |
| 252 | + req.Header.Set("Authorization", "Bearer "+token) | |
| 253 | + case "api_key": | |
| 254 | + key := os.Getenv(c.server.Auth.APIKeyEnv) | |
| 255 | + if key == "" { | |
| 256 | + return fmt.Errorf("missing API key for env var %s", c.server.Auth.APIKeyEnv) | |
| 257 | + } | |
| 258 | + header := c.server.Auth.APIKeyHeader | |
| 259 | + if header == "" { | |
| 260 | + header = "Authorization" | |
| 261 | + } | |
| 262 | + req.Header.Set(header, key) | |
| 263 | + default: | |
| 264 | + return fmt.Errorf("unknown auth type: %s", c.server.Auth.Type) | |
| 265 | + } | |
| 266 | + return nil | |
| 267 | +} | |
| 268 | +``` | |
| 269 | + | |
| 270 | +--- | |
| 271 | + | |
| 272 | +## Permission System | |
| 273 | + | |
| 274 | +**Decision:** Default to `ask` permission mode, allowing `auto` as an option. | |
| 275 | + | |
| 276 | +**Rationale:** | |
| 277 | + | |
| 278 | +1. **Safety First:** MCP tools can be powerful (code access, repository changes, etc.) | |
| 279 | +2. **User Control:** Users should explicitly allow MCP tool execution | |
| 280 | +3. **Flexibility:** Allow `auto` for trusted environments | |
| 281 | +4. **Consistency:** Matches mini-me's existing permission philosophy | |
| 282 | + | |
| 283 | +**Permission Modes:** | |
| 284 | + | |
| 285 | +| Mode | Behavior | Use Case | | |
| 286 | +|------|----------|----------| | |
| 287 | +| `ask` | Prompt user before each MCP tool call | Production, new users | | |
| 288 | +| `auto` | Execute MCP tools automatically | Trusted environments, CI/CD | | |
| 289 | + | |
| 290 | +**Implementation:** | |
| 291 | + | |
| 292 | +```go | |
| 293 | +// Global default | |
| 294 | +mcp: | |
| 295 | + enabled: true | |
| 296 | + permissions: ask # Default | |
| 297 | + | |
| 298 | +// Per-server override | |
| 299 | +servers: | |
| 300 | + - name: wikipedia | |
| 301 | + # ... | |
| 302 | + permissions: auto # Override for this server | |
| 303 | +``` | |
| 304 | + | |
| 305 | +--- | |
| 306 | + | |
| 307 | +## Error Handling | |
| 308 | + | |
| 309 | +**Decision:** Fail gracefully - skip problematic servers/tools with warnings. | |
| 310 | + | |
| 311 | +**Rationale:** | |
| 312 | + | |
| 313 | +1. **Resilience:** One bad MCP server shouldn't prevent mini-me from starting | |
| 314 | +2. **User Awareness:** Warnings inform users of issues | |
| 315 | +3. **Debugging:** Clear error messages help diagnose problems | |
| 316 | +4. **Safety:** Prevents cascading failures | |
| 317 | + | |
| 318 | +**Implementation:** | |
| 319 | + | |
| 320 | +- **Server level:** If a server fails to connect or authenticate, skip it and log warning | |
| 321 | +- **Tool level:** If a tool fails to parse or is binary-only, skip it and log warning | |
| 322 | +- **Discovery level:** If tool discovery fails, skip the server and log warning | |
| 323 | +- **Execution level:** If tool execution fails, return error to user | |
| 324 | + | |
| 325 | +--- | |
| 326 | + | |
| 327 | +## SSE Parsing | |
| 328 | + | |
| 329 | +**Decision:** Implement custom SSE parsing with large buffer for GitHub MCP server. | |
| 330 | + | |
| 331 | +**Rationale:** | |
| 332 | + | |
| 333 | +1. **GitHub's Large Responses:** GitHub MCP server returns tools with base64-encoded icons | |
| 334 | +2. **Default Limit:** Go's `bufio.Scanner` has a 64KB token limit | |
| 335 | +3. **GitHub Exceeds:** GitHub's response is >64KB, causing "token too long" errors | |
| 336 | +4. **Solution:** Use `bufio.Reader` with 10MB buffer instead of `bufio.Scanner` | |
| 337 | + | |
| 338 | +**Implementation:** | |
| 339 | + | |
| 340 | +```go | |
| 341 | +func (c *HTTPClient) parseSSEResponse(body io.Reader) (*mcpResponse, error) { | |
| 342 | + const maxLineSize = 10 * 1024 * 1024 // 10 MB | |
| 343 | + reader := bufio.NewReaderSize(body, maxLineSize) | |
| 344 | + | |
| 345 | + for { | |
| 346 | + line, err := reader.ReadString('\n') | |
| 347 | + if err != nil { | |
| 348 | + if err == io.EOF { | |
| 349 | + break | |
| 350 | + } | |
| 351 | + return nil, fmt.Errorf("failed to read SSE stream: %w", err) | |
| 352 | + } | |
| 353 | + // Parse line... | |
| 354 | + } | |
| 355 | + return nil, fmt.Errorf("no valid JSON-RPC response found in SSE stream") | |
| 356 | +} | |
| 357 | +``` | |
| 358 | + | |
| 359 | +--- | |
| 360 | + | |
| 361 | +## Summary | |
| 362 | + | |
| 363 | +| Decision | Rationale | Future | | |
| 364 | +|----------|-----------|--------| | |
| 365 | +| HTTP-only transport | Simplicity, covers all major servers | Add stdio later | | |
| 366 | +| Text-only support | Design philosophy, safety, simplicity | Keep text-only | | |
| 367 | +| No stdio transport | HTTP sufficient, complexity | Add if needed | | |
| 368 | +| No OAuth 2.0 | Complexity, current servers use tokens | Add if required | | |
| 369 | +| No caching | Freshness, simplicity | Add if needed | | |
| 370 | +| No lazy discovery | UX, error detection, simplicity | Add as option | | |
| 371 | +| Tool name prefixing | Avoid collisions, clarity | Keep | | |
| 372 | +| Tool filtering | Focus, safety, performance | Keep | | |
| 373 | +| Env var auth | Security, best practice | Keep | | |
| 374 | +| Permission system | Safety first, flexibility | Keep | | |
| 375 | +| Graceful errors | Resilience, user awareness | Keep | | |
| 376 | +| Custom SSE parsing | Handle large responses | Keep | | |
| 377 | + | |
| 378 | +--- | |
| 379 | + | |
| 380 | +## See Also | |
| 381 | + | |
| 382 | +- [MCP Configuration Reference](configuration.md) | |
| 383 | +- [Available MCP Tools](available-tools.md) | |
| 384 | +- [MCP Architecture](architecture.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,384 @@ | |||
| 1 | +# MCP Design Decisions | ||
| 2 | + | ||
| 3 | +This document explains the key design decisions made when implementing MCP (Model Context Protocol) support in mini-me. | ||
| 4 | + | ||
| 5 | +## Table of Contents | ||
| 6 | + | ||
| 7 | +- [HTTP-Only Transport](#http-only-transport) | ||
| 8 | +- [Text-Only Support](#text-only-support) | ||
| 9 | +- [No Stdio Transport (Yet)](#no-stdio-transport-yet) | ||
| 10 | +- [No OAuth 2.0 Support](#no-oauth-20-support) | ||
| 11 | +- [No Caching](#no-caching) | ||
| 12 | +- [No Lazy Discovery](#no-lazy-discovery) | ||
| 13 | +- [Tool Name Prefixing](#tool-name-prefixing) | ||
| 14 | +- [Tool Filtering](#tool-filtering) | ||
| 15 | +- [Authentication via Environment Variables](#authentication-via-environment-variables) | ||
| 16 | +- [Permission System](#permission-system) | ||
| 17 | +- [Error Handling](#error-handling) | ||
| 18 | +- [SSE Parsing](#sse-parsing) | ||
| 19 | + | ||
| 20 | +--- | ||
| 21 | + | ||
| 22 | +## HTTP-Only Transport | ||
| 23 | + | ||
| 24 | +**Decision:** Only implement HTTP transport (streamable HTTP), not stdio. | ||
| 25 | + | ||
| 26 | +**Rationale:** | ||
| 27 | + | ||
| 28 | +1. **Simplicity:** HTTP transport is simpler to implement and debug | ||
| 29 | +2. **Common Use Case:** Most hosted MCP servers use HTTP transport | ||
| 30 | +3. **Security:** HTTP over TLS provides built-in security | ||
| 31 | +4. **Compatibility:** Works with all hosted MCP services (GitHub, Context7, Wikipedia, etc.) | ||
| 32 | +5. **Implementation Effort:** Stdio requires subprocess management, which adds complexity | ||
| 33 | + | ||
| 34 | +**Trade-offs:** | ||
| 35 | + | ||
| 36 | +- Cannot use local stdio-based MCP servers | ||
| 37 | +- Cannot use Docker-based MCP servers that only expose stdio | ||
| 38 | + | ||
| 39 | +**Future:** Stdio transport will be added when there's demand for local MCP servers. | ||
| 40 | + | ||
| 41 | +--- | ||
| 42 | + | ||
| 43 | +## Text-Only Support | ||
| 44 | + | ||
| 45 | +**Decision:** Only support MCP tools that use text input/output. Filter out binary tools. | ||
| 46 | + | ||
| 47 | +**Rationale:** | ||
| 48 | + | ||
| 49 | +1. **Design Philosophy:** Mini-me is designed as a text-based agent | ||
| 50 | +2. **Safety:** Binary data can be dangerous (images, executables, etc.) | ||
| 51 | +3. **Simplicity:** Text is easier to handle, log, and display | ||
| 52 | +4. **Compatibility:** All major MCP servers (GitHub, Context7, Wikipedia) use text-only tools | ||
| 53 | +5. **Context:** Mini-me's context window is text-based | ||
| 54 | + | ||
| 55 | +**Implementation:** | ||
| 56 | + | ||
| 57 | +```go | ||
| 58 | +// In ConnectAll() after discovering tools | ||
| 59 | +for _, tool := range tools { | ||
| 60 | + if !tool.IsTextOnly() { | ||
| 61 | + warnings = append(warnings, | ||
| 62 | + fmt.Sprintf("mcp server %s: tool %s uses binary I/O - skipped (text-only)", | ||
| 63 | + server.Name, tool.Name)) | ||
| 64 | + continue | ||
| 65 | + } | ||
| 66 | + textTools = append(textTools, tool) | ||
| 67 | +} | ||
| 68 | +``` | ||
| 69 | + | ||
| 70 | +**Detection:** The `IsTextOnly()` method checks for: | ||
| 71 | +- Input parameters with `type: "string"` and `format: "binary"` | ||
| 72 | +- Input parameters with `type: "binary"` | ||
| 73 | +- Output properties with `type: "string"` and `format: "binary"` | ||
| 74 | +- Output properties with `type: "binary"` | ||
| 75 | + | ||
| 76 | +--- | ||
| 77 | + | ||
| 78 | +## No Stdio Transport (Yet) | ||
| 79 | + | ||
| 80 | +**Decision:** Defer stdio transport implementation. | ||
| 81 | + | ||
| 82 | +**Rationale:** | ||
| 83 | + | ||
| 84 | +1. **HTTP is Sufficient:** All currently known public MCP servers use HTTP | ||
| 85 | +2. **Complexity:** Stdio requires subprocess management, input/output streaming | ||
| 86 | +3. **Priorities:** Focus on HTTP transport first, add stdio later if needed | ||
| 87 | +4. **Use Cases:** Stdio is mainly for local/self-hosted MCP servers | ||
| 88 | + | ||
| 89 | +**Workaround:** Use HTTP-based MCP servers or run MCP servers as HTTP services. | ||
| 90 | + | ||
| 91 | +**Future:** Will implement when users request local stdio-based MCP servers. | ||
| 92 | + | ||
| 93 | +--- | ||
| 94 | + | ||
| 95 | +## No OAuth 2.0 Support | ||
| 96 | + | ||
| 97 | +**Decision:** Only support bearer token and API key authentication. No OAuth 2.0 flow. | ||
| 98 | + | ||
| 99 | +**Rationale:** | ||
| 100 | + | ||
| 101 | +1. **Complexity:** OAuth 2.0 requires browser interaction, token refresh, etc. | ||
| 102 | +2. **Headless:** Mini-me often runs in headless environments (CI/CD, scripts) | ||
| 103 | +3. **Current Servers:** Major MCP servers (GitHub, Context7) support bearer tokens | ||
| 104 | +4. **Security:** Bearer tokens are sufficient for most use cases | ||
| 105 | +5. **Implementation Effort:** OAuth 2.0 flow is complex to implement correctly | ||
| 106 | + | ||
| 107 | +**Trade-offs:** | ||
| 108 | + | ||
| 109 | +- Cannot use MCP servers that require OAuth 2.0 | ||
| 110 | +- GitHub MCP server works with PAT (Personal Access Token), not OAuth | ||
| 111 | + | ||
| 112 | +**Workaround:** Use bearer tokens (PAT) instead of OAuth for GitHub. | ||
| 113 | + | ||
| 114 | +**Future:** OAuth 2.0 will be added if a major MCP server requires it. | ||
| 115 | + | ||
| 116 | +--- | ||
| 117 | + | ||
| 118 | +## No Caching | ||
| 119 | + | ||
| 120 | +**Decision:** Don't cache discovered tools between runs. | ||
| 121 | + | ||
| 122 | +**Rationale:** | ||
| 123 | + | ||
| 124 | +1. **Freshness:** MCP servers may update their tools, caching could cause stale data | ||
| 125 | +2. **Simplicity:** No need to manage cache invalidation, storage, etc. | ||
| 126 | +3. **Startup Time:** Discovery is fast enough for most use cases | ||
| 127 | +4. **Memory:** Caching would use more memory for minimal benefit | ||
| 128 | +5. **Correctness:** Always have the latest tool list | ||
| 129 | + | ||
| 130 | +**Trade-offs:** | ||
| 131 | + | ||
| 132 | +- Slightly longer startup time (but discovery is parallel) | ||
| 133 | +- Cannot use mini-me if MCP servers are offline at startup | ||
| 134 | + | ||
| 135 | +**Future:** May add optional caching if startup time becomes an issue. | ||
| 136 | + | ||
| 137 | +--- | ||
| 138 | + | ||
| 139 | +## No Lazy Discovery | ||
| 140 | + | ||
| 141 | +**Decision:** Discover all MCP tools at startup, not lazily. | ||
| 142 | + | ||
| 143 | +**Rationale:** | ||
| 144 | + | ||
| 145 | +1. **User Experience:** Users see all available tools immediately | ||
| 146 | +2. **Error Detection:** Can detect configuration errors at startup | ||
| 147 | +3. **Simplicity:** Easier to implement and reason about | ||
| 148 | +4. **Consistency:** All tools available from first use | ||
| 149 | +5. **Validation:** Can validate tool schemas at startup | ||
| 150 | + | ||
| 151 | +**Trade-offs:** | ||
| 152 | + | ||
| 153 | +- Longer startup time (mitigated by parallel discovery) | ||
| 154 | +- All MCP servers must be accessible at startup | ||
| 155 | + | ||
| 156 | +**Future:** May add lazy discovery as an option for slow MCP servers. | ||
| 157 | + | ||
| 158 | +--- | ||
| 159 | + | ||
| 160 | +## Tool Name Prefixing | ||
| 161 | + | ||
| 162 | +**Decision:** Prefix all MCP tool names with their server name. | ||
| 163 | + | ||
| 164 | +**Rationale:** | ||
| 165 | + | ||
| 166 | +1. **Avoid Collisions:** Different MCP servers might have tools with the same name | ||
| 167 | +2. **Clarity:** Users can see which server a tool comes from | ||
| 168 | +3. **Namespacing:** Creates a natural namespace for MCP tools | ||
| 169 | +4. **Discoverability:** Makes it clear where tools come from | ||
| 170 | + | ||
| 171 | +**Implementation:** | ||
| 172 | + | ||
| 173 | +```go | ||
| 174 | +func SanitizeToolName(serverName, toolName string) (string, error) { | ||
| 175 | + sanitized := sanitizeName(toolName) | ||
| 176 | + fullName := serverName + "_" + sanitized | ||
| 177 | + // Check for collision with built-in tools | ||
| 178 | + if IsBuiltinTool(fullName) { | ||
| 179 | + return "", fmt.Errorf("tool name %q collides with built-in tool", fullName) | ||
| 180 | + } | ||
| 181 | + return fullName, nil | ||
| 182 | +} | ||
| 183 | +``` | ||
| 184 | + | ||
| 185 | +**Examples:** | ||
| 186 | +- Server: `github`, Tool: `list_repositories` → `github_list_repositories` | ||
| 187 | +- Server: `wikipedia`, Tool: `search_wikipedia` → `wikipedia_search_wikipedia` | ||
| 188 | +- Server: `context7`, Tool: `resolve-library-id` → `context7_resolve_library_id` | ||
| 189 | + | ||
| 190 | +**Name Sanitization:** | ||
| 191 | +- Non-alphanumeric characters → `_` | ||
| 192 | +- Leading/trailing `_` removed | ||
| 193 | +- Multiple consecutive `_` collapsed to one | ||
| 194 | + | ||
| 195 | +--- | ||
| 196 | + | ||
| 197 | +## Tool Filtering | ||
| 198 | + | ||
| 199 | +**Decision:** Allow users to filter which tools from each MCP server are exposed. | ||
| 200 | + | ||
| 201 | +**Rationale:** | ||
| 202 | + | ||
| 203 | +1. **Focus:** Users may only need a subset of available tools | ||
| 204 | +2. **Safety:** Prevent accidental use of powerful tools | ||
| 205 | +3. **Performance:** Reduces tool list size, speeds up startup | ||
| 206 | +4. **Customization:** Users can customize their tool set | ||
| 207 | + | ||
| 208 | +**Implementation:** | ||
| 209 | + | ||
| 210 | +```yaml | ||
| 211 | +servers: | ||
| 212 | + - name: github | ||
| 213 | + transport: http | ||
| 214 | + url: https://api.githubcopilot.com/mcp/ | ||
| 215 | + auth: | ||
| 216 | + type: bearer | ||
| 217 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 218 | + tools: # Only these tools will be available | ||
| 219 | + - get_me | ||
| 220 | + - list_repositories | ||
| 221 | + - search_code | ||
| 222 | +``` | ||
| 223 | + | ||
| 224 | +**Default:** If `tools` list is empty or not specified, all text-only tools are exposed. | ||
| 225 | + | ||
| 226 | +--- | ||
| 227 | + | ||
| 228 | +## Authentication via Environment Variables | ||
| 229 | + | ||
| 230 | +**Decision:** All authentication tokens/keys are loaded from environment variables. | ||
| 231 | + | ||
| 232 | +**Rationale:** | ||
| 233 | + | ||
| 234 | +1. **Security:** Prevents accidental commit of secrets to version control | ||
| 235 | +2. **Best Practice:** Follows the 12-factor app methodology | ||
| 236 | +3. **Flexibility:** Allows different tokens for different environments | ||
| 237 | +4. **Simplicity:** Easy to use with existing secret management | ||
| 238 | +5. **Compatibility:** Works with Docker, Kubernetes, CI/CD systems | ||
| 239 | + | ||
| 240 | +**Implementation:** | ||
| 241 | + | ||
| 242 | +```go | ||
| 243 | +func (c *HTTPClient) addAuthHeaders(req *http.Request) error { | ||
| 244 | + switch c.server.Auth.Type { | ||
| 245 | + case "none", "": | ||
| 246 | + return nil | ||
| 247 | + case "bearer": | ||
| 248 | + token := os.Getenv(c.server.Auth.TokenEnv) | ||
| 249 | + if token == "" { | ||
| 250 | + return fmt.Errorf("missing bearer token for env var %s", c.server.Auth.TokenEnv) | ||
| 251 | + } | ||
| 252 | + req.Header.Set("Authorization", "Bearer "+token) | ||
| 253 | + case "api_key": | ||
| 254 | + key := os.Getenv(c.server.Auth.APIKeyEnv) | ||
| 255 | + if key == "" { | ||
| 256 | + return fmt.Errorf("missing API key for env var %s", c.server.Auth.APIKeyEnv) | ||
| 257 | + } | ||
| 258 | + header := c.server.Auth.APIKeyHeader | ||
| 259 | + if header == "" { | ||
| 260 | + header = "Authorization" | ||
| 261 | + } | ||
| 262 | + req.Header.Set(header, key) | ||
| 263 | + default: | ||
| 264 | + return fmt.Errorf("unknown auth type: %s", c.server.Auth.Type) | ||
| 265 | + } | ||
| 266 | + return nil | ||
| 267 | +} | ||
| 268 | +``` | ||
| 269 | + | ||
| 270 | +--- | ||
| 271 | + | ||
| 272 | +## Permission System | ||
| 273 | + | ||
| 274 | +**Decision:** Default to `ask` permission mode, allowing `auto` as an option. | ||
| 275 | + | ||
| 276 | +**Rationale:** | ||
| 277 | + | ||
| 278 | +1. **Safety First:** MCP tools can be powerful (code access, repository changes, etc.) | ||
| 279 | +2. **User Control:** Users should explicitly allow MCP tool execution | ||
| 280 | +3. **Flexibility:** Allow `auto` for trusted environments | ||
| 281 | +4. **Consistency:** Matches mini-me's existing permission philosophy | ||
| 282 | + | ||
| 283 | +**Permission Modes:** | ||
| 284 | + | ||
| 285 | +| Mode | Behavior | Use Case | | ||
| 286 | +|------|----------|----------| | ||
| 287 | +| `ask` | Prompt user before each MCP tool call | Production, new users | | ||
| 288 | +| `auto` | Execute MCP tools automatically | Trusted environments, CI/CD | | ||
| 289 | + | ||
| 290 | +**Implementation:** | ||
| 291 | + | ||
| 292 | +```go | ||
| 293 | +// Global default | ||
| 294 | +mcp: | ||
| 295 | + enabled: true | ||
| 296 | + permissions: ask # Default | ||
| 297 | + | ||
| 298 | +// Per-server override | ||
| 299 | +servers: | ||
| 300 | + - name: wikipedia | ||
| 301 | + # ... | ||
| 302 | + permissions: auto # Override for this server | ||
| 303 | +``` | ||
| 304 | + | ||
| 305 | +--- | ||
| 306 | + | ||
| 307 | +## Error Handling | ||
| 308 | + | ||
| 309 | +**Decision:** Fail gracefully - skip problematic servers/tools with warnings. | ||
| 310 | + | ||
| 311 | +**Rationale:** | ||
| 312 | + | ||
| 313 | +1. **Resilience:** One bad MCP server shouldn't prevent mini-me from starting | ||
| 314 | +2. **User Awareness:** Warnings inform users of issues | ||
| 315 | +3. **Debugging:** Clear error messages help diagnose problems | ||
| 316 | +4. **Safety:** Prevents cascading failures | ||
| 317 | + | ||
| 318 | +**Implementation:** | ||
| 319 | + | ||
| 320 | +- **Server level:** If a server fails to connect or authenticate, skip it and log warning | ||
| 321 | +- **Tool level:** If a tool fails to parse or is binary-only, skip it and log warning | ||
| 322 | +- **Discovery level:** If tool discovery fails, skip the server and log warning | ||
| 323 | +- **Execution level:** If tool execution fails, return error to user | ||
| 324 | + | ||
| 325 | +--- | ||
| 326 | + | ||
| 327 | +## SSE Parsing | ||
| 328 | + | ||
| 329 | +**Decision:** Implement custom SSE parsing with large buffer for GitHub MCP server. | ||
| 330 | + | ||
| 331 | +**Rationale:** | ||
| 332 | + | ||
| 333 | +1. **GitHub's Large Responses:** GitHub MCP server returns tools with base64-encoded icons | ||
| 334 | +2. **Default Limit:** Go's `bufio.Scanner` has a 64KB token limit | ||
| 335 | +3. **GitHub Exceeds:** GitHub's response is >64KB, causing "token too long" errors | ||
| 336 | +4. **Solution:** Use `bufio.Reader` with 10MB buffer instead of `bufio.Scanner` | ||
| 337 | + | ||
| 338 | +**Implementation:** | ||
| 339 | + | ||
| 340 | +```go | ||
| 341 | +func (c *HTTPClient) parseSSEResponse(body io.Reader) (*mcpResponse, error) { | ||
| 342 | + const maxLineSize = 10 * 1024 * 1024 // 10 MB | ||
| 343 | + reader := bufio.NewReaderSize(body, maxLineSize) | ||
| 344 | + | ||
| 345 | + for { | ||
| 346 | + line, err := reader.ReadString('\n') | ||
| 347 | + if err != nil { | ||
| 348 | + if err == io.EOF { | ||
| 349 | + break | ||
| 350 | + } | ||
| 351 | + return nil, fmt.Errorf("failed to read SSE stream: %w", err) | ||
| 352 | + } | ||
| 353 | + // Parse line... | ||
| 354 | + } | ||
| 355 | + return nil, fmt.Errorf("no valid JSON-RPC response found in SSE stream") | ||
| 356 | +} | ||
| 357 | +``` | ||
| 358 | + | ||
| 359 | +--- | ||
| 360 | + | ||
| 361 | +## Summary | ||
| 362 | + | ||
| 363 | +| Decision | Rationale | Future | | ||
| 364 | +|----------|-----------|--------| | ||
| 365 | +| HTTP-only transport | Simplicity, covers all major servers | Add stdio later | | ||
| 366 | +| Text-only support | Design philosophy, safety, simplicity | Keep text-only | | ||
| 367 | +| No stdio transport | HTTP sufficient, complexity | Add if needed | | ||
| 368 | +| No OAuth 2.0 | Complexity, current servers use tokens | Add if required | | ||
| 369 | +| No caching | Freshness, simplicity | Add if needed | | ||
| 370 | +| No lazy discovery | UX, error detection, simplicity | Add as option | | ||
| 371 | +| Tool name prefixing | Avoid collisions, clarity | Keep | | ||
| 372 | +| Tool filtering | Focus, safety, performance | Keep | | ||
| 373 | +| Env var auth | Security, best practice | Keep | | ||
| 374 | +| Permission system | Safety first, flexibility | Keep | | ||
| 375 | +| Graceful errors | Resilience, user awareness | Keep | | ||
| 376 | +| Custom SSE parsing | Handle large responses | Keep | | ||
| 377 | + | ||
| 378 | +--- | ||
| 379 | + | ||
| 380 | +## See Also | ||
| 381 | + | ||
| 382 | +- [MCP Configuration Reference](configuration.md) | ||
| 383 | +- [Available MCP Tools](available-tools.md) | ||
| 384 | +- [MCP Architecture](architecture.md) | ||
added
docs/en/how-to/mcp-use-context7.md +70 -0 | new file mode 100644 | ||
| @@ -0,0 +1,70 @@ | ||
| 1 | +# How to use Context7 for documentation | |
| 2 | + | |
| 3 | +This guide shows how to use the Context7 MCP server to search and query technical documentation. | |
| 4 | + | |
| 5 | +## Prerequisites | |
| 6 | +- MCP enabled in mini-me | |
| 7 | +- A valid Context7 API key: `CONTEXT7_API_KEY` | |
| 8 | +- Context7 server configured in your configuration file | |
| 9 | + | |
| 10 | +## Steps | |
| 11 | + | |
| 12 | +### 1. Get a Library ID | |
| 13 | + | |
| 14 | +Before you can query documentation, you must obtain a **Context7 Library ID**. | |
| 15 | + | |
| 16 | +Use the `context7_resolve_library_id` tool with two required parameters: | |
| 17 | +- `query` : What you're looking for (e.g., "React hooks") | |
| 18 | +- `libraryName` : The library name (e.g., "react") | |
| 19 | + | |
| 20 | +**Example query:** | |
| 21 | +``` | |
| 22 | +"I want to understand how useEffect works in React" | |
| 23 | +``` | |
| 24 | + | |
| 25 | +The model should call: | |
| 26 | +``` | |
| 27 | +context7_resolve_library_id(query="useEffect React", libraryName="react") | |
| 28 | +``` | |
| 29 | + | |
| 30 | +**Expected result:** A library ID in the format `/org/project` (e.g., `/facebook/react`) | |
| 31 | + | |
| 32 | +### 2. Query the documentation | |
| 33 | + | |
| 34 | +Once you have the library ID, use `context7_query_docs` with: | |
| 35 | +- `libraryId` : The ID obtained in step 1 (e.g., `/facebook/react`) | |
| 36 | +- `query` : Your specific question (e.g., "useEffect cleanup function") | |
| 37 | + | |
| 38 | +**Example query:** | |
| 39 | +``` | |
| 40 | +"How to clean up a useEffect in React?" | |
| 41 | +``` | |
| 42 | + | |
| 43 | +The model should call: | |
| 44 | +``` | |
| 45 | +context7_query_docs(libraryId="/facebook/react", query="useEffect cleanup") | |
| 46 | +``` | |
| 47 | + | |
| 48 | +## Common Use Cases | |
| 49 | + | |
| 50 | +| Need | Example Query | Tools Used | | |
| 51 | +|------|---------------|------------| | |
| 52 | +| Understand a library | "Explain how Redux works" | resolve-library-id, query-docs | | |
| 53 | +| Find examples | "Show me useState examples" | resolve-library-id, query-docs | | |
| 54 | +| Solve a problem | "Why does my useEffect run multiple times?" | resolve-library-id, query-docs | | |
| 55 | +| Explore an API | "What methods are available in axios?" | resolve-library-id, query-docs | | |
| 56 | + | |
| 57 | +## Variants | |
| 58 | + | |
| 59 | +- **If you already know the library ID**: You can directly call `context7_query_docs` without using `resolve-library-id` | |
| 60 | +- **For multiple libraries**: Call `resolve-library-id` for each library, then `query-docs` for each one | |
| 61 | + | |
| 62 | +## Limitations | |
| 63 | + | |
| 64 | +- Maximum **3 calls** to `resolve-library-id` per question | |
| 65 | +- Maximum **3 calls** to `query-docs` per question | |
| 66 | +- Results depend on the library coverage in Context7's database | |
| 67 | + | |
| 68 | +## See Also | |
| 69 | +- Tools reference: [Available Tools](../reference/available-tools.md) | |
| 70 | +- Why Context7: [Design Decisions](../explanation/decisions.md#context7) | |
| new file mode 100644 | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | +# How to use Context7 for documentation | ||
| 2 | + | ||
| 3 | +This guide shows how to use the Context7 MCP server to search and query technical documentation. | ||
| 4 | + | ||
| 5 | +## Prerequisites | ||
| 6 | +- MCP enabled in mini-me | ||
| 7 | +- A valid Context7 API key: `CONTEXT7_API_KEY` | ||
| 8 | +- Context7 server configured in your configuration file | ||
| 9 | + | ||
| 10 | +## Steps | ||
| 11 | + | ||
| 12 | +### 1. Get a Library ID | ||
| 13 | + | ||
| 14 | +Before you can query documentation, you must obtain a **Context7 Library ID**. | ||
| 15 | + | ||
| 16 | +Use the `context7_resolve_library_id` tool with two required parameters: | ||
| 17 | +- `query` : What you're looking for (e.g., "React hooks") | ||
| 18 | +- `libraryName` : The library name (e.g., "react") | ||
| 19 | + | ||
| 20 | +**Example query:** | ||
| 21 | +``` | ||
| 22 | +"I want to understand how useEffect works in React" | ||
| 23 | +``` | ||
| 24 | + | ||
| 25 | +The model should call: | ||
| 26 | +``` | ||
| 27 | +context7_resolve_library_id(query="useEffect React", libraryName="react") | ||
| 28 | +``` | ||
| 29 | + | ||
| 30 | +**Expected result:** A library ID in the format `/org/project` (e.g., `/facebook/react`) | ||
| 31 | + | ||
| 32 | +### 2. Query the documentation | ||
| 33 | + | ||
| 34 | +Once you have the library ID, use `context7_query_docs` with: | ||
| 35 | +- `libraryId` : The ID obtained in step 1 (e.g., `/facebook/react`) | ||
| 36 | +- `query` : Your specific question (e.g., "useEffect cleanup function") | ||
| 37 | + | ||
| 38 | +**Example query:** | ||
| 39 | +``` | ||
| 40 | +"How to clean up a useEffect in React?" | ||
| 41 | +``` | ||
| 42 | + | ||
| 43 | +The model should call: | ||
| 44 | +``` | ||
| 45 | +context7_query_docs(libraryId="/facebook/react", query="useEffect cleanup") | ||
| 46 | +``` | ||
| 47 | + | ||
| 48 | +## Common Use Cases | ||
| 49 | + | ||
| 50 | +| Need | Example Query | Tools Used | | ||
| 51 | +|------|---------------|------------| | ||
| 52 | +| Understand a library | "Explain how Redux works" | resolve-library-id, query-docs | | ||
| 53 | +| Find examples | "Show me useState examples" | resolve-library-id, query-docs | | ||
| 54 | +| Solve a problem | "Why does my useEffect run multiple times?" | resolve-library-id, query-docs | | ||
| 55 | +| Explore an API | "What methods are available in axios?" | resolve-library-id, query-docs | | ||
| 56 | + | ||
| 57 | +## Variants | ||
| 58 | + | ||
| 59 | +- **If you already know the library ID**: You can directly call `context7_query_docs` without using `resolve-library-id` | ||
| 60 | +- **For multiple libraries**: Call `resolve-library-id` for each library, then `query-docs` for each one | ||
| 61 | + | ||
| 62 | +## Limitations | ||
| 63 | + | ||
| 64 | +- Maximum **3 calls** to `resolve-library-id` per question | ||
| 65 | +- Maximum **3 calls** to `query-docs` per question | ||
| 66 | +- Results depend on the library coverage in Context7's database | ||
| 67 | + | ||
| 68 | +## See Also | ||
| 69 | +- Tools reference: [Available Tools](../reference/available-tools.md) | ||
| 70 | +- Why Context7: [Design Decisions](../explanation/decisions.md#context7) | ||
added
docs/en/how-to/mcp-use-github.md +201 -0 | new file mode 100644 | ||
| @@ -0,0 +1,201 @@ | ||
| 1 | +# Use GitHub for Code Access | |
| 2 | + | |
| 3 | +This guide shows how to use the GitHub MCP server to interact with GitHub repositories, issues, pull requests, and more. | |
| 4 | + | |
| 5 | +## Prerequisites | |
| 6 | + | |
| 7 | +- MCP enabled in your mini-me configuration | |
| 8 | +- GitHub MCP server configured | |
| 9 | +- **GitHub Personal Access Token** with appropriate scopes | |
| 10 | + | |
| 11 | +## Configuration Example | |
| 12 | + | |
| 13 | +Add this to your `agent.yaml` or custom config file: | |
| 14 | + | |
| 15 | +```yaml | |
| 16 | +mcp: | |
| 17 | + enabled: true | |
| 18 | + servers: | |
| 19 | + - name: github | |
| 20 | + transport: http | |
| 21 | + url: https://api.githubcopilot.com/mcp/ | |
| 22 | + timeout: 60s | |
| 23 | + auth: | |
| 24 | + type: bearer | |
| 25 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 26 | + headers: | |
| 27 | + Accept: "application/json, text/event-stream" | |
| 28 | + # Optional: Filter which tools to expose | |
| 29 | + tools: | |
| 30 | + - get_me | |
| 31 | + - list_repositories | |
| 32 | + - list_issues | |
| 33 | + - list_pull_requests | |
| 34 | + - get_file_contents | |
| 35 | + - search_code | |
| 36 | + - search_repositories | |
| 37 | + - create_pull_request | |
| 38 | + - add_issue_comment | |
| 39 | +``` | |
| 40 | + | |
| 41 | +## Authentication Setup | |
| 42 | + | |
| 43 | +1. **Get a GitHub PAT:** | |
| 44 | + - Go to [https://github.com/settings/tokens](https://github.com/settings/tokens) | |
| 45 | + - Click "Generate new token" | |
| 46 | + - Select "Classic" token type | |
| 47 | + | |
| 48 | +2. **Required Scopes:** | |
| 49 | + - For repository access: `repo` (full control of private repositories) | |
| 50 | + - For reading user profile: `user` (read:user) | |
| 51 | + - For reading organizations: `read:org` | |
| 52 | + - For issues and pull requests: `repo` or `public_repo` | |
| 53 | + - For code search: `repo` | |
| 54 | + | |
| 55 | +3. **Set the environment variable:** | |
| 56 | + ```bash | |
| 57 | + export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_your_token_here | |
| 58 | + ``` | |
| 59 | + | |
| 60 | +## Available Tools | |
| 61 | + | |
| 62 | +The GitHub MCP server provides many tools organized by category: | |
| 63 | + | |
| 64 | +### Repository Information | |
| 65 | +- `github_get_me` - Get authenticated user profile | |
| 66 | +- `github_list_repositories` - List user repositories | |
| 67 | +- `github_get_repository` - Get repository details | |
| 68 | + | |
| 69 | +### Code Access | |
| 70 | +- `github_get_file_contents` - Get file content from a repository | |
| 71 | +- `github_list_branches` - List repository branches | |
| 72 | +- `github_list_commits` - List repository commits | |
| 73 | +- `github_get_commit` - Get commit details | |
| 74 | + | |
| 75 | +### Issues and Pull Requests | |
| 76 | +- `github_list_issues` - List repository issues | |
| 77 | +- `github_get_issue_details` - Get issue details (using `issue_read` method) | |
| 78 | +- `github_list_pull_requests` - List repository pull requests | |
| 79 | +- `github_get_pull_request` - Get pull request details (using `pull_request_read` method) | |
| 80 | +- `github_create_pull_request` - Create a new pull request | |
| 81 | +- `github_add_issue_comment` - Add a comment to an issue | |
| 82 | +- `github_update_issue_comment` - Update an existing comment | |
| 83 | + | |
| 84 | +### Search | |
| 85 | +- `github_search_code` - Search code across repositories | |
| 86 | +- `github_search_issues` - Search issues using semantic matching | |
| 87 | +- `github_search_pull_requests` - Search pull requests | |
| 88 | +- `github_search_repositories` - Search for repositories | |
| 89 | +- `github_search_commits` - Search for commits | |
| 90 | +- `github_search_users` - Search for users | |
| 91 | + | |
| 92 | +### Repository Management | |
| 93 | +- `github_create_repository` - Create a new repository | |
| 94 | +- `github_create_branch` - Create a new branch | |
| 95 | +- `github_fork_repository` - Fork a repository | |
| 96 | +- `github_merge_pull_request` - Merge a pull request | |
| 97 | +- `github_delete_file` - Delete a file from a repository | |
| 98 | + | |
| 99 | +### Advanced | |
| 100 | +- `github_run_secret_scanning` - Scan content for secrets | |
| 101 | +- `github_assign_copilot_to_issue` - Assign GitHub Copilot to an issue | |
| 102 | +- `github_get_copilot_job_status` - Get Copilot job status | |
| 103 | +- `github_create_pull_request_with_copilot` - Create PR with Copilot agent | |
| 104 | +- `github_request_copilot_review` - Request Copilot code review | |
| 105 | + | |
| 106 | +## Usage Examples | |
| 107 | + | |
| 108 | +### Get Your Profile | |
| 109 | + | |
| 110 | +``` | |
| 111 | +Use the github_get_me tool to get my GitHub user information | |
| 112 | +``` | |
| 113 | + | |
| 114 | +### List Your Repositories | |
| 115 | + | |
| 116 | +``` | |
| 117 | +Use the github_list_repositories tool to list all my repositories | |
| 118 | +``` | |
| 119 | + | |
| 120 | +### Search for Code | |
| 121 | + | |
| 122 | +``` | |
| 123 | +Use the github_search_code tool to find references to "context" in the golang/go repository | |
| 124 | +``` | |
| 125 | + | |
| 126 | +### Get File Contents | |
| 127 | + | |
| 128 | +``` | |
| 129 | +Use the github_get_file_contents tool to get the README.md file from owner/repo on the main branch | |
| 130 | +``` | |
| 131 | + | |
| 132 | +### Create a Pull Request | |
| 133 | + | |
| 134 | +``` | |
| 135 | +Use the github_create_pull_request tool to create a PR in owner/repo with title "Fix bug", head "feature-branch", base "main" | |
| 136 | +``` | |
| 137 | + | |
| 138 | +### Add Comment to Issue | |
| 139 | + | |
| 140 | +``` | |
| 141 | +Use the github_add_issue_comment tool to add a comment "Thanks for reporting!" to issue #123 in owner/repo | |
| 142 | +``` | |
| 143 | + | |
| 144 | +## Tool Parameters | |
| 145 | + | |
| 146 | +Most GitHub tools require these common parameters: | |
| 147 | + | |
| 148 | +- `owner` - Repository owner (username or organization) | |
| 149 | +- `repo` - Repository name | |
| 150 | +- Often both are marked with `x-mcp-header` to indicate they should be passed as headers | |
| 151 | + | |
| 152 | +For example: | |
| 153 | +```json | |
| 154 | +{ | |
| 155 | + "owner": "octocat", | |
| 156 | + "repo": "Hello-World" | |
| 157 | +} | |
| 158 | +``` | |
| 159 | + | |
| 160 | +## Tips | |
| 161 | + | |
| 162 | +1. **Be specific with owner/repo:** Always verify the owner and repository name before proceeding | |
| 163 | +2. **Use filters:** When searching, use GitHub's search qualifiers like `language:go`, `org:`, `user:` | |
| 164 | +3. **Handle large responses:** Some tools return large amounts of data; consider filtering or limiting results | |
| 165 | +4. **Check scopes:** If a tool fails with authentication error, your PAT may lack the required scope | |
| 166 | + | |
| 167 | +## Common Issues | |
| 168 | + | |
| 169 | +### Missing Required Parameters | |
| 170 | + | |
| 171 | +Many GitHub tools require specific parameters. Check the tool description for required fields. | |
| 172 | + | |
| 173 | +### Authentication Errors | |
| 174 | + | |
| 175 | +If you see "missing bearer token" or "401 Unauthorized": | |
| 176 | +- Verify `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable is set | |
| 177 | +- Check your token has the required scopes | |
| 178 | +- Ensure the token hasn't expired | |
| 179 | + | |
| 180 | +### Rate Limiting | |
| 181 | + | |
| 182 | +GitHub has rate limits. If you hit them: | |
| 183 | +- Wait before making more requests | |
| 184 | +- Consider using a fine-grained PAT with specific repository access | |
| 185 | +- Check your rate limit status at https://api.github.com/rate_limit | |
| 186 | + | |
| 187 | +## Troubleshooting | |
| 188 | + | |
| 189 | +If GitHub tools are not appearing: | |
| 190 | +1. Verify MCP is enabled in your configuration | |
| 191 | +2. Check that `GITHUB_PERSONAL_ACCESS_TOKEN` is exported in your environment | |
| 192 | +3. Look for warnings about GitHub server at startup | |
| 193 | +4. Test your token: `curl -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" https://api.github.com/user` | |
| 194 | +5. Test the MCP endpoint: `curl -X POST https://api.githubcopilot.com/mcp/ -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'` | |
| 195 | + | |
| 196 | +## See Also | |
| 197 | + | |
| 198 | +- [Getting Started with MCP](tutorials/getting-started.md) | |
| 199 | +- [Use Context7 for documentation](use-context7.md) | |
| 200 | +- [Use Wikipedia for research](use-wikipedia.md) | |
| 201 | +- [MCP Configuration Reference](reference/configuration.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,201 @@ | |||
| 1 | +# Use GitHub for Code Access | ||
| 2 | + | ||
| 3 | +This guide shows how to use the GitHub MCP server to interact with GitHub repositories, issues, pull requests, and more. | ||
| 4 | + | ||
| 5 | +## Prerequisites | ||
| 6 | + | ||
| 7 | +- MCP enabled in your mini-me configuration | ||
| 8 | +- GitHub MCP server configured | ||
| 9 | +- **GitHub Personal Access Token** with appropriate scopes | ||
| 10 | + | ||
| 11 | +## Configuration Example | ||
| 12 | + | ||
| 13 | +Add this to your `agent.yaml` or custom config file: | ||
| 14 | + | ||
| 15 | +```yaml | ||
| 16 | +mcp: | ||
| 17 | + enabled: true | ||
| 18 | + servers: | ||
| 19 | + - name: github | ||
| 20 | + transport: http | ||
| 21 | + url: https://api.githubcopilot.com/mcp/ | ||
| 22 | + timeout: 60s | ||
| 23 | + auth: | ||
| 24 | + type: bearer | ||
| 25 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 26 | + headers: | ||
| 27 | + Accept: "application/json, text/event-stream" | ||
| 28 | + # Optional: Filter which tools to expose | ||
| 29 | + tools: | ||
| 30 | + - get_me | ||
| 31 | + - list_repositories | ||
| 32 | + - list_issues | ||
| 33 | + - list_pull_requests | ||
| 34 | + - get_file_contents | ||
| 35 | + - search_code | ||
| 36 | + - search_repositories | ||
| 37 | + - create_pull_request | ||
| 38 | + - add_issue_comment | ||
| 39 | +``` | ||
| 40 | + | ||
| 41 | +## Authentication Setup | ||
| 42 | + | ||
| 43 | +1. **Get a GitHub PAT:** | ||
| 44 | + - Go to [https://github.com/settings/tokens](https://github.com/settings/tokens) | ||
| 45 | + - Click "Generate new token" | ||
| 46 | + - Select "Classic" token type | ||
| 47 | + | ||
| 48 | +2. **Required Scopes:** | ||
| 49 | + - For repository access: `repo` (full control of private repositories) | ||
| 50 | + - For reading user profile: `user` (read:user) | ||
| 51 | + - For reading organizations: `read:org` | ||
| 52 | + - For issues and pull requests: `repo` or `public_repo` | ||
| 53 | + - For code search: `repo` | ||
| 54 | + | ||
| 55 | +3. **Set the environment variable:** | ||
| 56 | + ```bash | ||
| 57 | + export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_your_token_here | ||
| 58 | + ``` | ||
| 59 | + | ||
| 60 | +## Available Tools | ||
| 61 | + | ||
| 62 | +The GitHub MCP server provides many tools organized by category: | ||
| 63 | + | ||
| 64 | +### Repository Information | ||
| 65 | +- `github_get_me` - Get authenticated user profile | ||
| 66 | +- `github_list_repositories` - List user repositories | ||
| 67 | +- `github_get_repository` - Get repository details | ||
| 68 | + | ||
| 69 | +### Code Access | ||
| 70 | +- `github_get_file_contents` - Get file content from a repository | ||
| 71 | +- `github_list_branches` - List repository branches | ||
| 72 | +- `github_list_commits` - List repository commits | ||
| 73 | +- `github_get_commit` - Get commit details | ||
| 74 | + | ||
| 75 | +### Issues and Pull Requests | ||
| 76 | +- `github_list_issues` - List repository issues | ||
| 77 | +- `github_get_issue_details` - Get issue details (using `issue_read` method) | ||
| 78 | +- `github_list_pull_requests` - List repository pull requests | ||
| 79 | +- `github_get_pull_request` - Get pull request details (using `pull_request_read` method) | ||
| 80 | +- `github_create_pull_request` - Create a new pull request | ||
| 81 | +- `github_add_issue_comment` - Add a comment to an issue | ||
| 82 | +- `github_update_issue_comment` - Update an existing comment | ||
| 83 | + | ||
| 84 | +### Search | ||
| 85 | +- `github_search_code` - Search code across repositories | ||
| 86 | +- `github_search_issues` - Search issues using semantic matching | ||
| 87 | +- `github_search_pull_requests` - Search pull requests | ||
| 88 | +- `github_search_repositories` - Search for repositories | ||
| 89 | +- `github_search_commits` - Search for commits | ||
| 90 | +- `github_search_users` - Search for users | ||
| 91 | + | ||
| 92 | +### Repository Management | ||
| 93 | +- `github_create_repository` - Create a new repository | ||
| 94 | +- `github_create_branch` - Create a new branch | ||
| 95 | +- `github_fork_repository` - Fork a repository | ||
| 96 | +- `github_merge_pull_request` - Merge a pull request | ||
| 97 | +- `github_delete_file` - Delete a file from a repository | ||
| 98 | + | ||
| 99 | +### Advanced | ||
| 100 | +- `github_run_secret_scanning` - Scan content for secrets | ||
| 101 | +- `github_assign_copilot_to_issue` - Assign GitHub Copilot to an issue | ||
| 102 | +- `github_get_copilot_job_status` - Get Copilot job status | ||
| 103 | +- `github_create_pull_request_with_copilot` - Create PR with Copilot agent | ||
| 104 | +- `github_request_copilot_review` - Request Copilot code review | ||
| 105 | + | ||
| 106 | +## Usage Examples | ||
| 107 | + | ||
| 108 | +### Get Your Profile | ||
| 109 | + | ||
| 110 | +``` | ||
| 111 | +Use the github_get_me tool to get my GitHub user information | ||
| 112 | +``` | ||
| 113 | + | ||
| 114 | +### List Your Repositories | ||
| 115 | + | ||
| 116 | +``` | ||
| 117 | +Use the github_list_repositories tool to list all my repositories | ||
| 118 | +``` | ||
| 119 | + | ||
| 120 | +### Search for Code | ||
| 121 | + | ||
| 122 | +``` | ||
| 123 | +Use the github_search_code tool to find references to "context" in the golang/go repository | ||
| 124 | +``` | ||
| 125 | + | ||
| 126 | +### Get File Contents | ||
| 127 | + | ||
| 128 | +``` | ||
| 129 | +Use the github_get_file_contents tool to get the README.md file from owner/repo on the main branch | ||
| 130 | +``` | ||
| 131 | + | ||
| 132 | +### Create a Pull Request | ||
| 133 | + | ||
| 134 | +``` | ||
| 135 | +Use the github_create_pull_request tool to create a PR in owner/repo with title "Fix bug", head "feature-branch", base "main" | ||
| 136 | +``` | ||
| 137 | + | ||
| 138 | +### Add Comment to Issue | ||
| 139 | + | ||
| 140 | +``` | ||
| 141 | +Use the github_add_issue_comment tool to add a comment "Thanks for reporting!" to issue #123 in owner/repo | ||
| 142 | +``` | ||
| 143 | + | ||
| 144 | +## Tool Parameters | ||
| 145 | + | ||
| 146 | +Most GitHub tools require these common parameters: | ||
| 147 | + | ||
| 148 | +- `owner` - Repository owner (username or organization) | ||
| 149 | +- `repo` - Repository name | ||
| 150 | +- Often both are marked with `x-mcp-header` to indicate they should be passed as headers | ||
| 151 | + | ||
| 152 | +For example: | ||
| 153 | +```json | ||
| 154 | +{ | ||
| 155 | + "owner": "octocat", | ||
| 156 | + "repo": "Hello-World" | ||
| 157 | +} | ||
| 158 | +``` | ||
| 159 | + | ||
| 160 | +## Tips | ||
| 161 | + | ||
| 162 | +1. **Be specific with owner/repo:** Always verify the owner and repository name before proceeding | ||
| 163 | +2. **Use filters:** When searching, use GitHub's search qualifiers like `language:go`, `org:`, `user:` | ||
| 164 | +3. **Handle large responses:** Some tools return large amounts of data; consider filtering or limiting results | ||
| 165 | +4. **Check scopes:** If a tool fails with authentication error, your PAT may lack the required scope | ||
| 166 | + | ||
| 167 | +## Common Issues | ||
| 168 | + | ||
| 169 | +### Missing Required Parameters | ||
| 170 | + | ||
| 171 | +Many GitHub tools require specific parameters. Check the tool description for required fields. | ||
| 172 | + | ||
| 173 | +### Authentication Errors | ||
| 174 | + | ||
| 175 | +If you see "missing bearer token" or "401 Unauthorized": | ||
| 176 | +- Verify `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable is set | ||
| 177 | +- Check your token has the required scopes | ||
| 178 | +- Ensure the token hasn't expired | ||
| 179 | + | ||
| 180 | +### Rate Limiting | ||
| 181 | + | ||
| 182 | +GitHub has rate limits. If you hit them: | ||
| 183 | +- Wait before making more requests | ||
| 184 | +- Consider using a fine-grained PAT with specific repository access | ||
| 185 | +- Check your rate limit status at https://api.github.com/rate_limit | ||
| 186 | + | ||
| 187 | +## Troubleshooting | ||
| 188 | + | ||
| 189 | +If GitHub tools are not appearing: | ||
| 190 | +1. Verify MCP is enabled in your configuration | ||
| 191 | +2. Check that `GITHUB_PERSONAL_ACCESS_TOKEN` is exported in your environment | ||
| 192 | +3. Look for warnings about GitHub server at startup | ||
| 193 | +4. Test your token: `curl -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" https://api.github.com/user` | ||
| 194 | +5. Test the MCP endpoint: `curl -X POST https://api.githubcopilot.com/mcp/ -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'` | ||
| 195 | + | ||
| 196 | +## See Also | ||
| 197 | + | ||
| 198 | +- [Getting Started with MCP](tutorials/getting-started.md) | ||
| 199 | +- [Use Context7 for documentation](use-context7.md) | ||
| 200 | +- [Use Wikipedia for research](use-wikipedia.md) | ||
| 201 | +- [MCP Configuration Reference](reference/configuration.md) | ||
added
docs/en/how-to/mcp-use-wikipedia.md +105 -0 | new file mode 100644 | ||
| @@ -0,0 +1,105 @@ | ||
| 1 | +# Use Wikipedia for Research | |
| 2 | + | |
| 3 | +This guide shows how to use the Wikipedia MCP server to search and retrieve information from Wikipedia. | |
| 4 | + | |
| 5 | +## Prerequisites | |
| 6 | + | |
| 7 | +- MCP enabled in your mini-me configuration | |
| 8 | +- Wikipedia MCP server configured (no authentication required) | |
| 9 | + | |
| 10 | +## Configuration Example | |
| 11 | + | |
| 12 | +Add this to your `agent.yaml` or custom config file: | |
| 13 | + | |
| 14 | +```yaml | |
| 15 | +mcp: | |
| 16 | + enabled: true | |
| 17 | + servers: | |
| 18 | + - name: wikipedia | |
| 19 | + transport: http | |
| 20 | + url: https://wikipedia-mcp.vercel.app/mcp | |
| 21 | + timeout: 30s | |
| 22 | + auth: | |
| 23 | + type: none | |
| 24 | +``` | |
| 25 | + | |
| 26 | +## Available Tools | |
| 27 | + | |
| 28 | +The Wikipedia MCP server provides these tools: | |
| 29 | + | |
| 30 | +| Tool | Description | | |
| 31 | +|------|-------------| | |
| 32 | +| `wikipedia_search_wikipedia` | Search Wikipedia articles by query | | |
| 33 | +| `wikipedia_get_article` | Get full article content by title | | |
| 34 | +| `wikipedia_get_summary` | Get article summary by title | | |
| 35 | +| `wikipedia_get_sections` | Get article sections/outline by title | | |
| 36 | +| `wikipedia_get_links` | Get links from an article | | |
| 37 | +| `wikipedia_get_coordinates` | Get geographic coordinates from an article | | |
| 38 | +| `wikipedia_get_related_topics` | Get related topics for an article | | |
| 39 | +| `wikipedia_summarize_article_for_query` | Summarize article sections relevant to a query | | |
| 40 | +| `wikipedia_summarize_article_section` | Summarize a specific section | | |
| 41 | +| `wikipedia_extract_key_facts` | Extract key facts from an article | | |
| 42 | +| `wikipedia_test_wikipedia_connectivity` | Test connection to Wikipedia | | |
| 43 | +| `wikipedia_list_supported_countries` | List supported Wikipedia language editions | | |
| 44 | +| `wikipedia_multi_search_wikipedia` | Search multiple Wikipedia editions | | |
| 45 | + | |
| 46 | +## Usage Examples | |
| 47 | + | |
| 48 | +### Search for Articles | |
| 49 | + | |
| 50 | +``` | |
| 51 | +Use the wikipedia_search_wikipedia tool to find articles about "artificial intelligence" | |
| 52 | +``` | |
| 53 | + | |
| 54 | +### Get Article Content | |
| 55 | + | |
| 56 | +``` | |
| 57 | +Use the wikipedia_get_article tool to retrieve the full content of "Go (programming language)" | |
| 58 | +``` | |
| 59 | + | |
| 60 | +### Get Article Summary | |
| 61 | + | |
| 62 | +``` | |
| 63 | +Use the wikipedia_get_summary tool to get a brief summary of "Machine learning" | |
| 64 | +``` | |
| 65 | + | |
| 66 | +### Get Article Structure | |
| 67 | + | |
| 68 | +``` | |
| 69 | +Use the wikipedia_get_sections tool to see the outline of "Python (programming language)" | |
| 70 | +``` | |
| 71 | + | |
| 72 | +## Tips | |
| 73 | + | |
| 74 | +- Wikipedia searches are case-insensitive | |
| 75 | +- Use specific queries for better results | |
| 76 | +- For programming topics, try including "programming language", "framework", or "library" in your query | |
| 77 | +- The Wikipedia MCP server supports multiple language editions | |
| 78 | + | |
| 79 | +## Language Support | |
| 80 | + | |
| 81 | +To search in a specific language edition of Wikipedia, you can: | |
| 82 | + | |
| 83 | +1. Use the `wikipedia_multi_search_wikipedia` tool with a language parameter | |
| 84 | +2. Specify the language in your query (e.g., "Python site:fr.wikipedia.org") | |
| 85 | + | |
| 86 | +## Rate Limits | |
| 87 | + | |
| 88 | +The Wikipedia MCP server has reasonable rate limits. For intensive usage, consider: | |
| 89 | +- Caching results locally | |
| 90 | +- Using the tool judiciously | |
| 91 | +- Reviewing the server's rate limit documentation | |
| 92 | + | |
| 93 | +## Troubleshooting | |
| 94 | + | |
| 95 | +If tools are not appearing: | |
| 96 | +1. Verify MCP is enabled in your configuration | |
| 97 | +2. Check that the Wikipedia server is correctly configured | |
| 98 | +3. Ensure there are no warnings about the Wikipedia server at startup | |
| 99 | +4. Verify your network connection allows access to `wikipedia-mcp.vercel.app` | |
| 100 | + | |
| 101 | +## See Also | |
| 102 | + | |
| 103 | +- [Getting Started with MCP](tutorials/getting-started.md) | |
| 104 | +- [Use Context7 for documentation](use-context7.md) | |
| 105 | +- [Use GitHub for code access](use-github.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | +# Use Wikipedia for Research | ||
| 2 | + | ||
| 3 | +This guide shows how to use the Wikipedia MCP server to search and retrieve information from Wikipedia. | ||
| 4 | + | ||
| 5 | +## Prerequisites | ||
| 6 | + | ||
| 7 | +- MCP enabled in your mini-me configuration | ||
| 8 | +- Wikipedia MCP server configured (no authentication required) | ||
| 9 | + | ||
| 10 | +## Configuration Example | ||
| 11 | + | ||
| 12 | +Add this to your `agent.yaml` or custom config file: | ||
| 13 | + | ||
| 14 | +```yaml | ||
| 15 | +mcp: | ||
| 16 | + enabled: true | ||
| 17 | + servers: | ||
| 18 | + - name: wikipedia | ||
| 19 | + transport: http | ||
| 20 | + url: https://wikipedia-mcp.vercel.app/mcp | ||
| 21 | + timeout: 30s | ||
| 22 | + auth: | ||
| 23 | + type: none | ||
| 24 | +``` | ||
| 25 | + | ||
| 26 | +## Available Tools | ||
| 27 | + | ||
| 28 | +The Wikipedia MCP server provides these tools: | ||
| 29 | + | ||
| 30 | +| Tool | Description | | ||
| 31 | +|------|-------------| | ||
| 32 | +| `wikipedia_search_wikipedia` | Search Wikipedia articles by query | | ||
| 33 | +| `wikipedia_get_article` | Get full article content by title | | ||
| 34 | +| `wikipedia_get_summary` | Get article summary by title | | ||
| 35 | +| `wikipedia_get_sections` | Get article sections/outline by title | | ||
| 36 | +| `wikipedia_get_links` | Get links from an article | | ||
| 37 | +| `wikipedia_get_coordinates` | Get geographic coordinates from an article | | ||
| 38 | +| `wikipedia_get_related_topics` | Get related topics for an article | | ||
| 39 | +| `wikipedia_summarize_article_for_query` | Summarize article sections relevant to a query | | ||
| 40 | +| `wikipedia_summarize_article_section` | Summarize a specific section | | ||
| 41 | +| `wikipedia_extract_key_facts` | Extract key facts from an article | | ||
| 42 | +| `wikipedia_test_wikipedia_connectivity` | Test connection to Wikipedia | | ||
| 43 | +| `wikipedia_list_supported_countries` | List supported Wikipedia language editions | | ||
| 44 | +| `wikipedia_multi_search_wikipedia` | Search multiple Wikipedia editions | | ||
| 45 | + | ||
| 46 | +## Usage Examples | ||
| 47 | + | ||
| 48 | +### Search for Articles | ||
| 49 | + | ||
| 50 | +``` | ||
| 51 | +Use the wikipedia_search_wikipedia tool to find articles about "artificial intelligence" | ||
| 52 | +``` | ||
| 53 | + | ||
| 54 | +### Get Article Content | ||
| 55 | + | ||
| 56 | +``` | ||
| 57 | +Use the wikipedia_get_article tool to retrieve the full content of "Go (programming language)" | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +### Get Article Summary | ||
| 61 | + | ||
| 62 | +``` | ||
| 63 | +Use the wikipedia_get_summary tool to get a brief summary of "Machine learning" | ||
| 64 | +``` | ||
| 65 | + | ||
| 66 | +### Get Article Structure | ||
| 67 | + | ||
| 68 | +``` | ||
| 69 | +Use the wikipedia_get_sections tool to see the outline of "Python (programming language)" | ||
| 70 | +``` | ||
| 71 | + | ||
| 72 | +## Tips | ||
| 73 | + | ||
| 74 | +- Wikipedia searches are case-insensitive | ||
| 75 | +- Use specific queries for better results | ||
| 76 | +- For programming topics, try including "programming language", "framework", or "library" in your query | ||
| 77 | +- The Wikipedia MCP server supports multiple language editions | ||
| 78 | + | ||
| 79 | +## Language Support | ||
| 80 | + | ||
| 81 | +To search in a specific language edition of Wikipedia, you can: | ||
| 82 | + | ||
| 83 | +1. Use the `wikipedia_multi_search_wikipedia` tool with a language parameter | ||
| 84 | +2. Specify the language in your query (e.g., "Python site:fr.wikipedia.org") | ||
| 85 | + | ||
| 86 | +## Rate Limits | ||
| 87 | + | ||
| 88 | +The Wikipedia MCP server has reasonable rate limits. For intensive usage, consider: | ||
| 89 | +- Caching results locally | ||
| 90 | +- Using the tool judiciously | ||
| 91 | +- Reviewing the server's rate limit documentation | ||
| 92 | + | ||
| 93 | +## Troubleshooting | ||
| 94 | + | ||
| 95 | +If tools are not appearing: | ||
| 96 | +1. Verify MCP is enabled in your configuration | ||
| 97 | +2. Check that the Wikipedia server is correctly configured | ||
| 98 | +3. Ensure there are no warnings about the Wikipedia server at startup | ||
| 99 | +4. Verify your network connection allows access to `wikipedia-mcp.vercel.app` | ||
| 100 | + | ||
| 101 | +## See Also | ||
| 102 | + | ||
| 103 | +- [Getting Started with MCP](tutorials/getting-started.md) | ||
| 104 | +- [Use Context7 for documentation](use-context7.md) | ||
| 105 | +- [Use GitHub for code access](use-github.md) | ||
added
docs/en/reference/mcp-authentication.md +276 -0 | new file mode 100644 | ||
| @@ -0,0 +1,276 @@ | ||
| 1 | +# MCP Authentication Reference | |
| 2 | + | |
| 3 | +This document describes the authentication methods supported by mini-me's MCP client. | |
| 4 | + | |
| 5 | +## Overview | |
| 6 | + | |
| 7 | +MCP servers can require different authentication methods. Mini-me currently supports: | |
| 8 | + | |
| 9 | +- **`none`**: No authentication (public servers) | |
| 10 | +- **`bearer`**: Bearer token authentication (HTTP Authorization header) | |
| 11 | +- **`api_key`**: API key authentication (custom header) | |
| 12 | + | |
| 13 | +## Authentication Types | |
| 14 | + | |
| 15 | +### Type: `none` | |
| 16 | + | |
| 17 | +**Use case:** Public MCP servers that don't require authentication. | |
| 18 | + | |
| 19 | +**Configuration:** | |
| 20 | +```yaml | |
| 21 | +servers: | |
| 22 | + - name: wikipedia | |
| 23 | + transport: http | |
| 24 | + url: https://wikipedia-mcp.vercel.app/mcp | |
| 25 | + auth: | |
| 26 | + type: none | |
| 27 | +``` | |
| 28 | + | |
| 29 | +**Behavior:** No authentication headers are added to requests. | |
| 30 | + | |
| 31 | +--- | |
| 32 | + | |
| 33 | +### Type: `bearer` | |
| 34 | + | |
| 35 | +**Use case:** MCP servers that use Bearer token authentication (most common). | |
| 36 | + | |
| 37 | +**Configuration:** | |
| 38 | +```yaml | |
| 39 | +servers: | |
| 40 | + - name: github | |
| 41 | + transport: http | |
| 42 | + url: https://api.githubcopilot.com/mcp/ | |
| 43 | + auth: | |
| 44 | + type: bearer | |
| 45 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 46 | +``` | |
| 47 | + | |
| 48 | +**Required:** `tokenEnv` - Name of the environment variable containing the token. | |
| 49 | + | |
| 50 | +**Behavior:** Adds `Authorization: Bearer {token}` header to all requests. | |
| 51 | + | |
| 52 | +**HTTP Header:** | |
| 53 | +``` | |
| 54 | +Authorization: Bearer ghp_your_token_here | |
| 55 | +``` | |
| 56 | + | |
| 57 | +**Supported servers:** | |
| 58 | +- GitHub MCP Server (`api.githubcopilot.com`) | |
| 59 | +- Context7 MCP Server (`mcp.context7.com`) | |
| 60 | +- Any server that accepts Bearer token authentication | |
| 61 | + | |
| 62 | +--- | |
| 63 | + | |
| 64 | +### Type: `api_key` | |
| 65 | + | |
| 66 | +**Use case:** MCP servers that use API key authentication with custom headers. | |
| 67 | + | |
| 68 | +**Configuration:** | |
| 69 | +```yaml | |
| 70 | +servers: | |
| 71 | + - name: duckduckgo | |
| 72 | + transport: http | |
| 73 | + url: https://mcp.hasdata.com/mcp | |
| 74 | + auth: | |
| 75 | + type: api_key | |
| 76 | + apiKeyEnv: HASDATA_API_KEY | |
| 77 | + apiKeyHeader: x-api-key | |
| 78 | +``` | |
| 79 | + | |
| 80 | +**Required:** | |
| 81 | +- `apiKeyEnv` - Name of the environment variable containing the API key | |
| 82 | +- `apiKeyHeader` - Name of the HTTP header to use | |
| 83 | + | |
| 84 | +**Optional:** If `apiKeyHeader` is not specified, defaults to `Authorization`. | |
| 85 | + | |
| 86 | +**Behavior:** Adds `{apiKeyHeader}: {api_key}` header to all requests. | |
| 87 | + | |
| 88 | +**HTTP Header:** | |
| 89 | +``` | |
| 90 | +X-API-Key: your_api_key_here | |
| 91 | +``` | |
| 92 | + | |
| 93 | +**Supported servers:** | |
| 94 | +- DuckDuckGo MCP Server via HasData | |
| 95 | + | |
| 96 | +--- | |
| 97 | + | |
| 98 | +## Not Yet Supported | |
| 99 | + | |
| 100 | +### OAuth 2.0 | |
| 101 | + | |
| 102 | +**Status:** Not yet implemented in mini-me. | |
| 103 | + | |
| 104 | +**Use case:** MCP servers that require OAuth 2.0 authentication flow (browser-based login). | |
| 105 | + | |
| 106 | +**Example server:** Some custom MCP servers that implement OAuth. | |
| 107 | + | |
| 108 | +**Workaround:** Use a self-hosted MCP server that accepts Bearer tokens or API keys instead. | |
| 109 | + | |
| 110 | +--- | |
| 111 | + | |
| 112 | +## Environment Variables | |
| 113 | + | |
| 114 | +Authentication tokens and API keys are loaded from environment variables. This is a security best practice that prevents secrets from being committed to version control. | |
| 115 | + | |
| 116 | +### Setting Environment Variables | |
| 117 | + | |
| 118 | +**Linux/macOS:** | |
| 119 | +```bash | |
| 120 | +# Temporary (current session) | |
| 121 | +export GITHUB_PERSONAL_ACCESS_TOKEN="ghp_your_token_here" | |
| 122 | + | |
| 123 | +# Permanent (add to ~/.bashrc or ~/.zshrc) | |
| 124 | +echo 'export GITHUB_PERSONAL_ACCESS_TOKEN="ghp_your_token_here"' >> ~/.bashrc | |
| 125 | +source ~/.bashrc | |
| 126 | + | |
| 127 | +# Single command | |
| 128 | +GITHUB_PERSONAL_ACCESS_TOKEN="ghp_your_token_here" mm --config your_config.yaml | |
| 129 | +``` | |
| 130 | + | |
| 131 | +**Windows (Command Prompt):** | |
| 132 | +```cmd | |
| 133 | +set GITHUB_PERSONAL_ACCESS_TOKEN=ghp_your_token_here | |
| 134 | +mm --config your_config.yaml | |
| 135 | +``` | |
| 136 | + | |
| 137 | +**Windows (PowerShell):** | |
| 138 | +```powershell | |
| 139 | +$env:GITHUB_PERSONAL_ACCESS_TOKEN="ghp_your_token_here" | |
| 140 | +mm --config your_config.yaml | |
| 141 | +``` | |
| 142 | + | |
| 143 | +### Best Practices | |
| 144 | + | |
| 145 | +1. **Never hardcode secrets:** Always use environment variables or secret management systems | |
| 146 | +2. **Use `.env` files carefully:** If using `.env` files, add them to `.gitignore` | |
| 147 | +3. **Limit permissions:** Use tokens/API keys with minimum required permissions | |
| 148 | +4. **Rotate regularly:** Rotate tokens and API keys regularly | |
| 149 | +5. **Audit access:** Regularly review which services have access to your tokens | |
| 150 | + | |
| 151 | +--- | |
| 152 | + | |
| 153 | +## Server-Specific Requirements | |
| 154 | + | |
| 155 | +### GitHub MCP Server | |
| 156 | + | |
| 157 | +**Endpoint:** `https://api.githubcopilot.com/mcp/` | |
| 158 | + | |
| 159 | +**Authentication:** Bearer token (`bearer` type) | |
| 160 | + | |
| 161 | +**Required scopes:** | |
| 162 | +- `read:user` - For user profile access (`get_me`) | |
| 163 | +- `repo` - For repository access (most tools) | |
| 164 | +- `read:org` - For organization access | |
| 165 | +- `user` - Basic user information | |
| 166 | + | |
| 167 | +**Token type:** Classic Personal Access Token (PAT) | |
| 168 | + | |
| 169 | +**Token generation:** | |
| 170 | +1. Go to [https://github.com/settings/tokens](https://github.com/settings/tokens) | |
| 171 | +2. Click "Generate new token (classic)" | |
| 172 | +3. Select required scopes | |
| 173 | +4. Generate token | |
| 174 | +5. Copy the token (it won't be shown again) | |
| 175 | + | |
| 176 | +--- | |
| 177 | + | |
| 178 | +### Context7 MCP Server | |
| 179 | + | |
| 180 | +**Endpoint:** `https://mcp.context7.com/mcp` | |
| 181 | + | |
| 182 | +**Authentication:** Bearer token (`bearer` type) | |
| 183 | + | |
| 184 | +**Token:** Context7 API key | |
| 185 | + | |
| 186 | +**Token generation:** | |
| 187 | +1. Sign up at [https://context7.com](https://context7.com) | |
| 188 | +2. Get your API key from the dashboard | |
| 189 | +3. Set as `CONTEXT7_API_KEY` environment variable | |
| 190 | + | |
| 191 | +--- | |
| 192 | + | |
| 193 | +### Wikipedia MCP Server | |
| 194 | + | |
| 195 | +**Endpoint:** `https://wikipedia-mcp.vercel.app/mcp` | |
| 196 | + | |
| 197 | +**Authentication:** None (`none` type) | |
| 198 | + | |
| 199 | +**Token:** Not required | |
| 200 | + | |
| 201 | +--- | |
| 202 | + | |
| 203 | +### HasData (DuckDuckGo) MCP Server | |
| 204 | + | |
| 205 | +**Endpoint:** `https://mcp.hasdata.com/mcp?apis=duckduckgo` | |
| 206 | + | |
| 207 | +**Authentication:** API key (`api_key` type) | |
| 208 | + | |
| 209 | +**Token:** HasData API key | |
| 210 | + | |
| 211 | +**Headers:** | |
| 212 | +- Header name: `x-api-key` | |
| 213 | +- Environment variable: `HASDATA_API_KEY` | |
| 214 | + | |
| 215 | +--- | |
| 216 | + | |
| 217 | +## Troubleshooting | |
| 218 | + | |
| 219 | +### "missing bearer token" or "missing API key" error | |
| 220 | + | |
| 221 | +**Cause:** The required environment variable is not set or is empty. | |
| 222 | + | |
| 223 | +**Solution:** | |
| 224 | +1. Verify the variable is set: `echo $VARIABLE_NAME` | |
| 225 | +2. Verify it's exported: `env | grep VARIABLE_NAME` | |
| 226 | +3. Check for typos in the variable name in your config | |
| 227 | + | |
| 228 | +### Authentication fails but token works with curl | |
| 229 | + | |
| 230 | +**Cause:** Different authentication requirements between REST API and MCP endpoint. | |
| 231 | + | |
| 232 | +**Example:** GitHub REST API (`api.github.com`) vs GitHub MCP (`api.githubcopilot.com`) | |
| 233 | + | |
| 234 | +**Solution:** | |
| 235 | +- Test directly with the MCP endpoint: | |
| 236 | + ```bash | |
| 237 | + curl -X POST https://api.githubcopilot.com/mcp/ \ | |
| 238 | + -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \ | |
| 239 | + -H "Accept: application/json, text/event-stream" \ | |
| 240 | + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | |
| 241 | + ``` | |
| 242 | +- Ensure your token has the required scopes for the MCP server | |
| 243 | + | |
| 244 | +### Server returns 403 Forbidden | |
| 245 | + | |
| 246 | +**Cause:** Token lacks required permissions or server-specific requirements. | |
| 247 | + | |
| 248 | +**Solution:** | |
| 249 | +- Check if the token has expired | |
| 250 | +- Verify the token has the correct scopes | |
| 251 | +- Some servers may require additional headers or parameters | |
| 252 | + | |
| 253 | +### Server connection times out | |
| 254 | + | |
| 255 | +**Solution:** | |
| 256 | +- Increase the `timeout` in your server configuration | |
| 257 | +- Check network connectivity to the MCP server endpoint | |
| 258 | +- Verify the server is online and accessible | |
| 259 | + | |
| 260 | +--- | |
| 261 | + | |
| 262 | +## Security Considerations | |
| 263 | + | |
| 264 | +1. **Token scope:** Only grant the minimum scopes required | |
| 265 | +2. **Token storage:** Use environment variables, not config files | |
| 266 | +3. **Token sharing:** Never share tokens or commit them to version control | |
| 267 | +4. **Token rotation:** Rotate tokens regularly, especially if compromised | |
| 268 | +5. **Network security:** Ensure connections to MCP servers use HTTPS | |
| 269 | + | |
| 270 | +--- | |
| 271 | + | |
| 272 | +## See Also | |
| 273 | + | |
| 274 | +- [MCP Configuration Reference](configuration.md) | |
| 275 | +- [Available MCP Tools](available-tools.md) | |
| 276 | +- [MCP Architecture](explanation/architecture.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,276 @@ | |||
| 1 | +# MCP Authentication Reference | ||
| 2 | + | ||
| 3 | +This document describes the authentication methods supported by mini-me's MCP client. | ||
| 4 | + | ||
| 5 | +## Overview | ||
| 6 | + | ||
| 7 | +MCP servers can require different authentication methods. Mini-me currently supports: | ||
| 8 | + | ||
| 9 | +- **`none`**: No authentication (public servers) | ||
| 10 | +- **`bearer`**: Bearer token authentication (HTTP Authorization header) | ||
| 11 | +- **`api_key`**: API key authentication (custom header) | ||
| 12 | + | ||
| 13 | +## Authentication Types | ||
| 14 | + | ||
| 15 | +### Type: `none` | ||
| 16 | + | ||
| 17 | +**Use case:** Public MCP servers that don't require authentication. | ||
| 18 | + | ||
| 19 | +**Configuration:** | ||
| 20 | +```yaml | ||
| 21 | +servers: | ||
| 22 | + - name: wikipedia | ||
| 23 | + transport: http | ||
| 24 | + url: https://wikipedia-mcp.vercel.app/mcp | ||
| 25 | + auth: | ||
| 26 | + type: none | ||
| 27 | +``` | ||
| 28 | + | ||
| 29 | +**Behavior:** No authentication headers are added to requests. | ||
| 30 | + | ||
| 31 | +--- | ||
| 32 | + | ||
| 33 | +### Type: `bearer` | ||
| 34 | + | ||
| 35 | +**Use case:** MCP servers that use Bearer token authentication (most common). | ||
| 36 | + | ||
| 37 | +**Configuration:** | ||
| 38 | +```yaml | ||
| 39 | +servers: | ||
| 40 | + - name: github | ||
| 41 | + transport: http | ||
| 42 | + url: https://api.githubcopilot.com/mcp/ | ||
| 43 | + auth: | ||
| 44 | + type: bearer | ||
| 45 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 46 | +``` | ||
| 47 | + | ||
| 48 | +**Required:** `tokenEnv` - Name of the environment variable containing the token. | ||
| 49 | + | ||
| 50 | +**Behavior:** Adds `Authorization: Bearer {token}` header to all requests. | ||
| 51 | + | ||
| 52 | +**HTTP Header:** | ||
| 53 | +``` | ||
| 54 | +Authorization: Bearer ghp_your_token_here | ||
| 55 | +``` | ||
| 56 | + | ||
| 57 | +**Supported servers:** | ||
| 58 | +- GitHub MCP Server (`api.githubcopilot.com`) | ||
| 59 | +- Context7 MCP Server (`mcp.context7.com`) | ||
| 60 | +- Any server that accepts Bearer token authentication | ||
| 61 | + | ||
| 62 | +--- | ||
| 63 | + | ||
| 64 | +### Type: `api_key` | ||
| 65 | + | ||
| 66 | +**Use case:** MCP servers that use API key authentication with custom headers. | ||
| 67 | + | ||
| 68 | +**Configuration:** | ||
| 69 | +```yaml | ||
| 70 | +servers: | ||
| 71 | + - name: duckduckgo | ||
| 72 | + transport: http | ||
| 73 | + url: https://mcp.hasdata.com/mcp | ||
| 74 | + auth: | ||
| 75 | + type: api_key | ||
| 76 | + apiKeyEnv: HASDATA_API_KEY | ||
| 77 | + apiKeyHeader: x-api-key | ||
| 78 | +``` | ||
| 79 | + | ||
| 80 | +**Required:** | ||
| 81 | +- `apiKeyEnv` - Name of the environment variable containing the API key | ||
| 82 | +- `apiKeyHeader` - Name of the HTTP header to use | ||
| 83 | + | ||
| 84 | +**Optional:** If `apiKeyHeader` is not specified, defaults to `Authorization`. | ||
| 85 | + | ||
| 86 | +**Behavior:** Adds `{apiKeyHeader}: {api_key}` header to all requests. | ||
| 87 | + | ||
| 88 | +**HTTP Header:** | ||
| 89 | +``` | ||
| 90 | +X-API-Key: your_api_key_here | ||
| 91 | +``` | ||
| 92 | + | ||
| 93 | +**Supported servers:** | ||
| 94 | +- DuckDuckGo MCP Server via HasData | ||
| 95 | + | ||
| 96 | +--- | ||
| 97 | + | ||
| 98 | +## Not Yet Supported | ||
| 99 | + | ||
| 100 | +### OAuth 2.0 | ||
| 101 | + | ||
| 102 | +**Status:** Not yet implemented in mini-me. | ||
| 103 | + | ||
| 104 | +**Use case:** MCP servers that require OAuth 2.0 authentication flow (browser-based login). | ||
| 105 | + | ||
| 106 | +**Example server:** Some custom MCP servers that implement OAuth. | ||
| 107 | + | ||
| 108 | +**Workaround:** Use a self-hosted MCP server that accepts Bearer tokens or API keys instead. | ||
| 109 | + | ||
| 110 | +--- | ||
| 111 | + | ||
| 112 | +## Environment Variables | ||
| 113 | + | ||
| 114 | +Authentication tokens and API keys are loaded from environment variables. This is a security best practice that prevents secrets from being committed to version control. | ||
| 115 | + | ||
| 116 | +### Setting Environment Variables | ||
| 117 | + | ||
| 118 | +**Linux/macOS:** | ||
| 119 | +```bash | ||
| 120 | +# Temporary (current session) | ||
| 121 | +export GITHUB_PERSONAL_ACCESS_TOKEN="ghp_your_token_here" | ||
| 122 | + | ||
| 123 | +# Permanent (add to ~/.bashrc or ~/.zshrc) | ||
| 124 | +echo 'export GITHUB_PERSONAL_ACCESS_TOKEN="ghp_your_token_here"' >> ~/.bashrc | ||
| 125 | +source ~/.bashrc | ||
| 126 | + | ||
| 127 | +# Single command | ||
| 128 | +GITHUB_PERSONAL_ACCESS_TOKEN="ghp_your_token_here" mm --config your_config.yaml | ||
| 129 | +``` | ||
| 130 | + | ||
| 131 | +**Windows (Command Prompt):** | ||
| 132 | +```cmd | ||
| 133 | +set GITHUB_PERSONAL_ACCESS_TOKEN=ghp_your_token_here | ||
| 134 | +mm --config your_config.yaml | ||
| 135 | +``` | ||
| 136 | + | ||
| 137 | +**Windows (PowerShell):** | ||
| 138 | +```powershell | ||
| 139 | +$env:GITHUB_PERSONAL_ACCESS_TOKEN="ghp_your_token_here" | ||
| 140 | +mm --config your_config.yaml | ||
| 141 | +``` | ||
| 142 | + | ||
| 143 | +### Best Practices | ||
| 144 | + | ||
| 145 | +1. **Never hardcode secrets:** Always use environment variables or secret management systems | ||
| 146 | +2. **Use `.env` files carefully:** If using `.env` files, add them to `.gitignore` | ||
| 147 | +3. **Limit permissions:** Use tokens/API keys with minimum required permissions | ||
| 148 | +4. **Rotate regularly:** Rotate tokens and API keys regularly | ||
| 149 | +5. **Audit access:** Regularly review which services have access to your tokens | ||
| 150 | + | ||
| 151 | +--- | ||
| 152 | + | ||
| 153 | +## Server-Specific Requirements | ||
| 154 | + | ||
| 155 | +### GitHub MCP Server | ||
| 156 | + | ||
| 157 | +**Endpoint:** `https://api.githubcopilot.com/mcp/` | ||
| 158 | + | ||
| 159 | +**Authentication:** Bearer token (`bearer` type) | ||
| 160 | + | ||
| 161 | +**Required scopes:** | ||
| 162 | +- `read:user` - For user profile access (`get_me`) | ||
| 163 | +- `repo` - For repository access (most tools) | ||
| 164 | +- `read:org` - For organization access | ||
| 165 | +- `user` - Basic user information | ||
| 166 | + | ||
| 167 | +**Token type:** Classic Personal Access Token (PAT) | ||
| 168 | + | ||
| 169 | +**Token generation:** | ||
| 170 | +1. Go to [https://github.com/settings/tokens](https://github.com/settings/tokens) | ||
| 171 | +2. Click "Generate new token (classic)" | ||
| 172 | +3. Select required scopes | ||
| 173 | +4. Generate token | ||
| 174 | +5. Copy the token (it won't be shown again) | ||
| 175 | + | ||
| 176 | +--- | ||
| 177 | + | ||
| 178 | +### Context7 MCP Server | ||
| 179 | + | ||
| 180 | +**Endpoint:** `https://mcp.context7.com/mcp` | ||
| 181 | + | ||
| 182 | +**Authentication:** Bearer token (`bearer` type) | ||
| 183 | + | ||
| 184 | +**Token:** Context7 API key | ||
| 185 | + | ||
| 186 | +**Token generation:** | ||
| 187 | +1. Sign up at [https://context7.com](https://context7.com) | ||
| 188 | +2. Get your API key from the dashboard | ||
| 189 | +3. Set as `CONTEXT7_API_KEY` environment variable | ||
| 190 | + | ||
| 191 | +--- | ||
| 192 | + | ||
| 193 | +### Wikipedia MCP Server | ||
| 194 | + | ||
| 195 | +**Endpoint:** `https://wikipedia-mcp.vercel.app/mcp` | ||
| 196 | + | ||
| 197 | +**Authentication:** None (`none` type) | ||
| 198 | + | ||
| 199 | +**Token:** Not required | ||
| 200 | + | ||
| 201 | +--- | ||
| 202 | + | ||
| 203 | +### HasData (DuckDuckGo) MCP Server | ||
| 204 | + | ||
| 205 | +**Endpoint:** `https://mcp.hasdata.com/mcp?apis=duckduckgo` | ||
| 206 | + | ||
| 207 | +**Authentication:** API key (`api_key` type) | ||
| 208 | + | ||
| 209 | +**Token:** HasData API key | ||
| 210 | + | ||
| 211 | +**Headers:** | ||
| 212 | +- Header name: `x-api-key` | ||
| 213 | +- Environment variable: `HASDATA_API_KEY` | ||
| 214 | + | ||
| 215 | +--- | ||
| 216 | + | ||
| 217 | +## Troubleshooting | ||
| 218 | + | ||
| 219 | +### "missing bearer token" or "missing API key" error | ||
| 220 | + | ||
| 221 | +**Cause:** The required environment variable is not set or is empty. | ||
| 222 | + | ||
| 223 | +**Solution:** | ||
| 224 | +1. Verify the variable is set: `echo $VARIABLE_NAME` | ||
| 225 | +2. Verify it's exported: `env | grep VARIABLE_NAME` | ||
| 226 | +3. Check for typos in the variable name in your config | ||
| 227 | + | ||
| 228 | +### Authentication fails but token works with curl | ||
| 229 | + | ||
| 230 | +**Cause:** Different authentication requirements between REST API and MCP endpoint. | ||
| 231 | + | ||
| 232 | +**Example:** GitHub REST API (`api.github.com`) vs GitHub MCP (`api.githubcopilot.com`) | ||
| 233 | + | ||
| 234 | +**Solution:** | ||
| 235 | +- Test directly with the MCP endpoint: | ||
| 236 | + ```bash | ||
| 237 | + curl -X POST https://api.githubcopilot.com/mcp/ \ | ||
| 238 | + -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \ | ||
| 239 | + -H "Accept: application/json, text/event-stream" \ | ||
| 240 | + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | ||
| 241 | + ``` | ||
| 242 | +- Ensure your token has the required scopes for the MCP server | ||
| 243 | + | ||
| 244 | +### Server returns 403 Forbidden | ||
| 245 | + | ||
| 246 | +**Cause:** Token lacks required permissions or server-specific requirements. | ||
| 247 | + | ||
| 248 | +**Solution:** | ||
| 249 | +- Check if the token has expired | ||
| 250 | +- Verify the token has the correct scopes | ||
| 251 | +- Some servers may require additional headers or parameters | ||
| 252 | + | ||
| 253 | +### Server connection times out | ||
| 254 | + | ||
| 255 | +**Solution:** | ||
| 256 | +- Increase the `timeout` in your server configuration | ||
| 257 | +- Check network connectivity to the MCP server endpoint | ||
| 258 | +- Verify the server is online and accessible | ||
| 259 | + | ||
| 260 | +--- | ||
| 261 | + | ||
| 262 | +## Security Considerations | ||
| 263 | + | ||
| 264 | +1. **Token scope:** Only grant the minimum scopes required | ||
| 265 | +2. **Token storage:** Use environment variables, not config files | ||
| 266 | +3. **Token sharing:** Never share tokens or commit them to version control | ||
| 267 | +4. **Token rotation:** Rotate tokens regularly, especially if compromised | ||
| 268 | +5. **Network security:** Ensure connections to MCP servers use HTTPS | ||
| 269 | + | ||
| 270 | +--- | ||
| 271 | + | ||
| 272 | +## See Also | ||
| 273 | + | ||
| 274 | +- [MCP Configuration Reference](configuration.md) | ||
| 275 | +- [Available MCP Tools](available-tools.md) | ||
| 276 | +- [MCP Architecture](explanation/architecture.md) | ||
added
docs/en/reference/mcp-available-tools.md +223 -0 | new file mode 100644 | ||
| @@ -0,0 +1,223 @@ | ||
| 1 | +# Available MCP Tools Reference | |
| 2 | + | |
| 3 | +This document lists the tools available from various MCP servers that can be connected to mini-me. | |
| 4 | + | |
| 5 | +## Overview | |
| 6 | + | |
| 7 | +Each MCP server provides its own set of tools. When connected, these tools are prefixed with the server name to avoid collisions. For example, the `list_repositories` tool from the GitHub server becomes `github_list_repositories`. | |
| 8 | + | |
| 9 | +## Tool Discovery | |
| 10 | + | |
| 11 | +Tools are discovered at mini-me startup. The discovery process: | |
| 12 | + | |
| 13 | +1. Connects to each configured MCP server | |
| 14 | +2. Requests the list of available tools via the MCP protocol | |
| 15 | +3. Filters out tools that use binary input/output (mini-me only supports text) | |
| 16 | +4. Optionally filters by the `tools` list in the server configuration | |
| 17 | +5. Sanitizes tool names to ensure they are valid identifiers | |
| 18 | + | |
| 19 | +## Server-Specific Tools | |
| 20 | + | |
| 21 | +### Wikipedia MCP Server | |
| 22 | + | |
| 23 | +**Server name:** `wikipedia` | |
| 24 | + | |
| 25 | +| Tool Name | Prefixed Name | Description | | |
| 26 | +|-----------|---------------|-------------| | |
| 27 | +| `search_wikipedia` | `wikipedia_search_wikipedia` | Search Wikipedia articles | | |
| 28 | +| `get_article` | `wikipedia_get_article` | Get full article content | | |
| 29 | +| `get_summary` | `wikipedia_get_summary` | Get article summary | | |
| 30 | +| `get_sections` | `wikipedia_get_sections` | Get article sections/outline | | |
| 31 | +| `get_links` | `wikipedia_get_links` | Get links from an article | | |
| 32 | +| `get_coordinates` | `wikipedia_get_coordinates` | Get geographic coordinates | | |
| 33 | +| `get_related_topics` | `wikipedia_get_related_topics` | Get related topics | | |
| 34 | +| `summarize_article_for_query` | `wikipedia_summarize_article_for_query` | Summarize relevant sections | | |
| 35 | +| `summarize_article_section` | `wikipedia_summarize_article_section` | Summarize a specific section | | |
| 36 | +| `extract_key_facts` | `wikipedia_extract_key_facts` | Extract key facts | | |
| 37 | +| `test_wikipedia_connectivity` | `wikipedia_test_wikipedia_connectivity` | Test connection | | |
| 38 | +| `list_supported_countries` | `wikipedia_list_supported_countries` | List supported languages | | |
| 39 | +| `multi_search_wikipedia` | `wikipedia_multi_search_wikipedia` | Multi-language search | | |
| 40 | + | |
| 41 | +### Context7 MCP Server | |
| 42 | + | |
| 43 | +**Server name:** `context7` | |
| 44 | + | |
| 45 | +| Tool Name | Prefixed Name | Description | | |
| 46 | +|-----------|---------------|-------------| | |
| 47 | +| `resolve-library-id` | `context7_resolve_library_id` | Resolve a library name to an ID | | |
| 48 | +| `query-docs` | `context7_query_docs` | Query documentation for a library | | |
| 49 | + | |
| 50 | +**Usage hints:** | |
| 51 | +- Call `resolve-library-id` first to get a library ID | |
| 52 | +- Use the library ID with `query-docs` to search documentation | |
| 53 | + | |
| 54 | +**Example workflow:** | |
| 55 | +``` | |
| 56 | +1. context7_resolve_library_id(query="React hooks", libraryName="react") | |
| 57 | +2. context7_query_docs(libraryId="/facebook/react", query="useEffect examples") | |
| 58 | +``` | |
| 59 | + | |
| 60 | +### GitHub MCP Server | |
| 61 | + | |
| 62 | +**Server name:** `github` | |
| 63 | + | |
| 64 | +#### Repository Tools | |
| 65 | +| Tool Name | Prefixed Name | Description | | |
| 66 | +|-----------|---------------|-------------| | |
| 67 | +| `get_me` | `github_get_me` | Get authenticated user profile | | |
| 68 | +| `list_repositories` | `github_list_repositories` | List user repositories | | |
| 69 | +| `list_repository_collaborators` | `github_list_repository_collaborators` | List repository collaborators | | |
| 70 | +| `create_repository` | `github_create_repository` | Create a new repository | | |
| 71 | +| `fork_repository` | `github_fork_repository` | Fork a repository | | |
| 72 | + | |
| 73 | +#### Code Tools | |
| 74 | +| Tool Name | Prefixed Name | Description | | |
| 75 | +|-----------|---------------|-------------| | |
| 76 | +| `get_file_contents` | `github_get_file_contents` | Get file content | | |
| 77 | +| `create_or_update_file` | `github_create_or_update_file` | Create or update a file | | |
| 78 | +| `delete_file` | `github_delete_file` | Delete a file | | |
| 79 | +| `list_branches` | `github_list_branches` | List repository branches | | |
| 80 | +| `list_commits` | `github_list_commits` | List commits | | |
| 81 | +| `get_commit` | `github_get_commit` | Get commit details | | |
| 82 | +| `create_branch` | `github_create_branch` | Create a new branch | | |
| 83 | +| `update_pull_request_branch` | `github_update_pull_request_branch` | Update PR branch | | |
| 84 | +| `push_files` | `github_push_files` | Push multiple files | | |
| 85 | + | |
| 86 | +#### Issue and Pull Request Tools | |
| 87 | +| Tool Name | Prefixed Name | Description | | |
| 88 | +|-----------|---------------|-------------| | |
| 89 | +| `list_issues` | `github_list_issues` | List repository issues | | |
| 90 | +| `issue_read` | `github_issue_read` | Read issue details | | |
| 91 | +| `issue_write` | `github_issue_write` | Create/update issue | | |
| 92 | +| `list_pull_requests` | `github_list_pull_requests` | List PRs | | |
| 93 | +| `pull_request_read` | `github_pull_request_read` | Read PR details | | |
| 94 | +| `pull_request_write` | `github_pull_request_write` | Write PR review | | |
| 95 | +| `pull_request_review_write` | `github_pull_request_review_write` | Create/submit PR review | | |
| 96 | +| `create_pull_request` | `github_create_pull_request` | Create a new PR | | |
| 97 | +| `merge_pull_request` | `github_merge_pull_request` | Merge a PR | | |
| 98 | +| `update_pull_request` | `github_update_pull_request` | Update PR | | |
| 99 | +| `create_or_update_issue` | `github_issue_write` | Create or update issue | | |
| 100 | +| `add_issue_comment` | `github_add_issue_comment` | Add comment to issue | | |
| 101 | +| `update_issue_comment` | `github_update_issue_comment` | Update issue comment | | |
| 102 | +| `add_comment_to_pending_review` | `github_add_comment_to_pending_review` | Add review comment | | |
| 103 | +| `add_reply_to_pull_request_comment` | `github_add_reply_to_pull_request_comment` | Reply to PR comment | | |
| 104 | +| `add_pull_request_comment` | `github_add_pull_request_comment` | Add PR comment | | |
| 105 | +| `get_copilot_job_status` | `github_get_copilot_job_status` | Get Copilot job status | | |
| 106 | +| `request_copilot_review` | `github_request_copilot_review` | Request Copilot review | | |
| 107 | + | |
| 108 | +#### Search Tools | |
| 109 | +| Tool Name | Prefixed Name | Description | | |
| 110 | +|-----------|---------------|-------------| | |
| 111 | +| `search_code` | `github_search_code` | Search code | | |
| 112 | +| `search_issues` | `github_search_issues` | Search issues | | |
| 113 | +| `search_pull_requests` | `github_search_pull_requests` | Search PRs | | |
| 114 | +| `search_repositories` | `github_search_repositories` | Search repositories | | |
| 115 | +| `search_commits` | `github_search_commits` | Search commits | | |
| 116 | +| `search_users` | `github_search_users` | Search users | | |
| 117 | + | |
| 118 | +#### Label and Milestone Tools | |
| 119 | +| Tool Name | Prefixed Name | Description | | |
| 120 | +|-----------|---------------|-------------| | |
| 121 | +| `get_label` | `github_get_label` | Get a specific label | | |
| 122 | +| `get_team_members` | `github_get_team_members` | Get team members | | |
| 123 | +| `get_teams` | `github_get_teams` | Get user teams | | |
| 124 | +| `list_issue_fields` | `github_list_issue_fields` | List issue fields | | |
| 125 | +| `list_issue_types` | `github_list_issue_types` | List issue types | | |
| 126 | +| `list_releases` | `github_list_releases` | List releases | | |
| 127 | +| `list_tags` | `github_list_tags` | List git tags | | |
| 128 | +| `get_release_by_tag` | `github_get_release_by_tag` | Get release by tag | | |
| 129 | +| `get_latest_release` | `github_get_latest_release` | Get latest release | | |
| 130 | +| `get_tag` | `github_get_tag` | Get tag details | | |
| 131 | + | |
| 132 | +#### Copilot Tools | |
| 133 | +| Tool Name | Prefixed Name | Description | | |
| 134 | +|-----------|---------------|-------------| | |
| 135 | +| `create_pull_request_with_copilot` | `github_create_pull_request_with_copilot` | Create PR with Copilot | | |
| 136 | +| `assign_copilot_to_issue` | `github_assign_copilot_to_issue` | Assign Copilot to issue | | |
| 137 | + | |
| 138 | +#### Sub-Issue Tools | |
| 139 | +| Tool Name | Prefixed Name | Description | | |
| 140 | +|-----------|---------------|-------------| | |
| 141 | +| `sub_issue_write` | `github_sub_issue_write` | Add/remove/reprioritize sub-issues | | |
| 142 | + | |
| 143 | +#### Secret Scanning | |
| 144 | +| Tool Name | Prefixed Name | Description | | |
| 145 | +|-----------|---------------|-------------| | |
| 146 | +| `run_secret_scanning` | `github_run_secret_scanning` | Run secret scanning | | |
| 147 | + | |
| 148 | +## Tool Name Prefixing | |
| 149 | + | |
| 150 | +All MCP tools are prefixed with their server name to avoid naming collisions. The format is: | |
| 151 | + | |
| 152 | +``` | |
| 153 | +{server_name}_{original_tool_name} | |
| 154 | +``` | |
| 155 | + | |
| 156 | +For example: | |
| 157 | +- Server: `wikipedia`, Tool: `search_wikipedia` → Prefixed: `wikipedia_search_wikipedia` | |
| 158 | +- Server: `github`, Tool: `list_repositories` → Prefixed: `github_list_repositories` | |
| 159 | +- Server: `context7`, Tool: `resolve-library-id` → Prefixed: `context7_resolve_library_id` (note: hyphens become underscores) | |
| 160 | + | |
| 161 | +## Tool Name Sanitization | |
| 162 | + | |
| 163 | +Tool names are sanitized to be valid Go identifiers: | |
| 164 | + | |
| 165 | +1. Non-alphanumeric characters are replaced with underscores (`_`) | |
| 166 | +2. Leading/trailing underscores are removed | |
| 167 | +3. Multiple consecutive underscores are collapsed to one | |
| 168 | +4. If the result is empty, it becomes `"tool"` | |
| 169 | + | |
| 170 | +Examples: | |
| 171 | +- `resolve-library-id` → `resolve_library_id` | |
| 172 | +- `query-docs` → `query_docs` | |
| 173 | +- `add-comment_to-pending-review` → `add_comment_to_pending_review` | |
| 174 | + | |
| 175 | +## Required Parameters | |
| 176 | + | |
| 177 | +Each tool has its own input schema defining required and optional parameters. Required parameters must be provided when calling the tool. | |
| 178 | + | |
| 179 | +To see the required parameters for a specific tool, use: | |
| 180 | +``` | |
| 181 | +Use the {tool_name} tool | |
| 182 | +``` | |
| 183 | + | |
| 184 | +Mini-me will show you the tool description including required parameters. | |
| 185 | + | |
| 186 | +## Tool Filtering | |
| 187 | + | |
| 188 | +You can filter which tools from a server are exposed by specifying a `tools` list in the server configuration: | |
| 189 | + | |
| 190 | +```yaml | |
| 191 | +servers: | |
| 192 | + - name: github | |
| 193 | + transport: http | |
| 194 | + url: https://api.githubcopilot.com/mcp/ | |
| 195 | + auth: | |
| 196 | + type: bearer | |
| 197 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 198 | + tools: # Only these tools will be available | |
| 199 | + - get_me | |
| 200 | + - list_repositories | |
| 201 | + - search_code | |
| 202 | +``` | |
| 203 | + | |
| 204 | +## Binary Tool Filtering | |
| 205 | + | |
| 206 | +Mini-me **only supports text input/output** for MCP tools. Any tool that requires binary data (images, audio, etc.) is automatically filtered out and a warning is displayed at startup. | |
| 207 | + | |
| 208 | +This is a safety measure to ensure compatibility with mini-me's text-only architecture. | |
| 209 | + | |
| 210 | +## Tool Execution | |
| 211 | + | |
| 212 | +When you use an MCP tool: | |
| 213 | + | |
| 214 | +1. **Permission check:** If permissions are set to `ask`, you'll be prompted to confirm | |
| 215 | +2. **Input validation:** Required parameters are validated | |
| 216 | +3. **Timeout:** The server's configured timeout applies | |
| 217 | +4. **Output:** Results are returned as text (non-text content is skipped) | |
| 218 | +5. **Truncation:** Output may be truncated if it exceeds `maxOutput` characters | |
| 219 | + | |
| 220 | +## See Also | |
| 221 | + | |
| 222 | +- [MCP Configuration](configuration.md) | |
| 223 | +- [MCP Architecture](explanation/architecture.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,223 @@ | |||
| 1 | +# Available MCP Tools Reference | ||
| 2 | + | ||
| 3 | +This document lists the tools available from various MCP servers that can be connected to mini-me. | ||
| 4 | + | ||
| 5 | +## Overview | ||
| 6 | + | ||
| 7 | +Each MCP server provides its own set of tools. When connected, these tools are prefixed with the server name to avoid collisions. For example, the `list_repositories` tool from the GitHub server becomes `github_list_repositories`. | ||
| 8 | + | ||
| 9 | +## Tool Discovery | ||
| 10 | + | ||
| 11 | +Tools are discovered at mini-me startup. The discovery process: | ||
| 12 | + | ||
| 13 | +1. Connects to each configured MCP server | ||
| 14 | +2. Requests the list of available tools via the MCP protocol | ||
| 15 | +3. Filters out tools that use binary input/output (mini-me only supports text) | ||
| 16 | +4. Optionally filters by the `tools` list in the server configuration | ||
| 17 | +5. Sanitizes tool names to ensure they are valid identifiers | ||
| 18 | + | ||
| 19 | +## Server-Specific Tools | ||
| 20 | + | ||
| 21 | +### Wikipedia MCP Server | ||
| 22 | + | ||
| 23 | +**Server name:** `wikipedia` | ||
| 24 | + | ||
| 25 | +| Tool Name | Prefixed Name | Description | | ||
| 26 | +|-----------|---------------|-------------| | ||
| 27 | +| `search_wikipedia` | `wikipedia_search_wikipedia` | Search Wikipedia articles | | ||
| 28 | +| `get_article` | `wikipedia_get_article` | Get full article content | | ||
| 29 | +| `get_summary` | `wikipedia_get_summary` | Get article summary | | ||
| 30 | +| `get_sections` | `wikipedia_get_sections` | Get article sections/outline | | ||
| 31 | +| `get_links` | `wikipedia_get_links` | Get links from an article | | ||
| 32 | +| `get_coordinates` | `wikipedia_get_coordinates` | Get geographic coordinates | | ||
| 33 | +| `get_related_topics` | `wikipedia_get_related_topics` | Get related topics | | ||
| 34 | +| `summarize_article_for_query` | `wikipedia_summarize_article_for_query` | Summarize relevant sections | | ||
| 35 | +| `summarize_article_section` | `wikipedia_summarize_article_section` | Summarize a specific section | | ||
| 36 | +| `extract_key_facts` | `wikipedia_extract_key_facts` | Extract key facts | | ||
| 37 | +| `test_wikipedia_connectivity` | `wikipedia_test_wikipedia_connectivity` | Test connection | | ||
| 38 | +| `list_supported_countries` | `wikipedia_list_supported_countries` | List supported languages | | ||
| 39 | +| `multi_search_wikipedia` | `wikipedia_multi_search_wikipedia` | Multi-language search | | ||
| 40 | + | ||
| 41 | +### Context7 MCP Server | ||
| 42 | + | ||
| 43 | +**Server name:** `context7` | ||
| 44 | + | ||
| 45 | +| Tool Name | Prefixed Name | Description | | ||
| 46 | +|-----------|---------------|-------------| | ||
| 47 | +| `resolve-library-id` | `context7_resolve_library_id` | Resolve a library name to an ID | | ||
| 48 | +| `query-docs` | `context7_query_docs` | Query documentation for a library | | ||
| 49 | + | ||
| 50 | +**Usage hints:** | ||
| 51 | +- Call `resolve-library-id` first to get a library ID | ||
| 52 | +- Use the library ID with `query-docs` to search documentation | ||
| 53 | + | ||
| 54 | +**Example workflow:** | ||
| 55 | +``` | ||
| 56 | +1. context7_resolve_library_id(query="React hooks", libraryName="react") | ||
| 57 | +2. context7_query_docs(libraryId="/facebook/react", query="useEffect examples") | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +### GitHub MCP Server | ||
| 61 | + | ||
| 62 | +**Server name:** `github` | ||
| 63 | + | ||
| 64 | +#### Repository Tools | ||
| 65 | +| Tool Name | Prefixed Name | Description | | ||
| 66 | +|-----------|---------------|-------------| | ||
| 67 | +| `get_me` | `github_get_me` | Get authenticated user profile | | ||
| 68 | +| `list_repositories` | `github_list_repositories` | List user repositories | | ||
| 69 | +| `list_repository_collaborators` | `github_list_repository_collaborators` | List repository collaborators | | ||
| 70 | +| `create_repository` | `github_create_repository` | Create a new repository | | ||
| 71 | +| `fork_repository` | `github_fork_repository` | Fork a repository | | ||
| 72 | + | ||
| 73 | +#### Code Tools | ||
| 74 | +| Tool Name | Prefixed Name | Description | | ||
| 75 | +|-----------|---------------|-------------| | ||
| 76 | +| `get_file_contents` | `github_get_file_contents` | Get file content | | ||
| 77 | +| `create_or_update_file` | `github_create_or_update_file` | Create or update a file | | ||
| 78 | +| `delete_file` | `github_delete_file` | Delete a file | | ||
| 79 | +| `list_branches` | `github_list_branches` | List repository branches | | ||
| 80 | +| `list_commits` | `github_list_commits` | List commits | | ||
| 81 | +| `get_commit` | `github_get_commit` | Get commit details | | ||
| 82 | +| `create_branch` | `github_create_branch` | Create a new branch | | ||
| 83 | +| `update_pull_request_branch` | `github_update_pull_request_branch` | Update PR branch | | ||
| 84 | +| `push_files` | `github_push_files` | Push multiple files | | ||
| 85 | + | ||
| 86 | +#### Issue and Pull Request Tools | ||
| 87 | +| Tool Name | Prefixed Name | Description | | ||
| 88 | +|-----------|---------------|-------------| | ||
| 89 | +| `list_issues` | `github_list_issues` | List repository issues | | ||
| 90 | +| `issue_read` | `github_issue_read` | Read issue details | | ||
| 91 | +| `issue_write` | `github_issue_write` | Create/update issue | | ||
| 92 | +| `list_pull_requests` | `github_list_pull_requests` | List PRs | | ||
| 93 | +| `pull_request_read` | `github_pull_request_read` | Read PR details | | ||
| 94 | +| `pull_request_write` | `github_pull_request_write` | Write PR review | | ||
| 95 | +| `pull_request_review_write` | `github_pull_request_review_write` | Create/submit PR review | | ||
| 96 | +| `create_pull_request` | `github_create_pull_request` | Create a new PR | | ||
| 97 | +| `merge_pull_request` | `github_merge_pull_request` | Merge a PR | | ||
| 98 | +| `update_pull_request` | `github_update_pull_request` | Update PR | | ||
| 99 | +| `create_or_update_issue` | `github_issue_write` | Create or update issue | | ||
| 100 | +| `add_issue_comment` | `github_add_issue_comment` | Add comment to issue | | ||
| 101 | +| `update_issue_comment` | `github_update_issue_comment` | Update issue comment | | ||
| 102 | +| `add_comment_to_pending_review` | `github_add_comment_to_pending_review` | Add review comment | | ||
| 103 | +| `add_reply_to_pull_request_comment` | `github_add_reply_to_pull_request_comment` | Reply to PR comment | | ||
| 104 | +| `add_pull_request_comment` | `github_add_pull_request_comment` | Add PR comment | | ||
| 105 | +| `get_copilot_job_status` | `github_get_copilot_job_status` | Get Copilot job status | | ||
| 106 | +| `request_copilot_review` | `github_request_copilot_review` | Request Copilot review | | ||
| 107 | + | ||
| 108 | +#### Search Tools | ||
| 109 | +| Tool Name | Prefixed Name | Description | | ||
| 110 | +|-----------|---------------|-------------| | ||
| 111 | +| `search_code` | `github_search_code` | Search code | | ||
| 112 | +| `search_issues` | `github_search_issues` | Search issues | | ||
| 113 | +| `search_pull_requests` | `github_search_pull_requests` | Search PRs | | ||
| 114 | +| `search_repositories` | `github_search_repositories` | Search repositories | | ||
| 115 | +| `search_commits` | `github_search_commits` | Search commits | | ||
| 116 | +| `search_users` | `github_search_users` | Search users | | ||
| 117 | + | ||
| 118 | +#### Label and Milestone Tools | ||
| 119 | +| Tool Name | Prefixed Name | Description | | ||
| 120 | +|-----------|---------------|-------------| | ||
| 121 | +| `get_label` | `github_get_label` | Get a specific label | | ||
| 122 | +| `get_team_members` | `github_get_team_members` | Get team members | | ||
| 123 | +| `get_teams` | `github_get_teams` | Get user teams | | ||
| 124 | +| `list_issue_fields` | `github_list_issue_fields` | List issue fields | | ||
| 125 | +| `list_issue_types` | `github_list_issue_types` | List issue types | | ||
| 126 | +| `list_releases` | `github_list_releases` | List releases | | ||
| 127 | +| `list_tags` | `github_list_tags` | List git tags | | ||
| 128 | +| `get_release_by_tag` | `github_get_release_by_tag` | Get release by tag | | ||
| 129 | +| `get_latest_release` | `github_get_latest_release` | Get latest release | | ||
| 130 | +| `get_tag` | `github_get_tag` | Get tag details | | ||
| 131 | + | ||
| 132 | +#### Copilot Tools | ||
| 133 | +| Tool Name | Prefixed Name | Description | | ||
| 134 | +|-----------|---------------|-------------| | ||
| 135 | +| `create_pull_request_with_copilot` | `github_create_pull_request_with_copilot` | Create PR with Copilot | | ||
| 136 | +| `assign_copilot_to_issue` | `github_assign_copilot_to_issue` | Assign Copilot to issue | | ||
| 137 | + | ||
| 138 | +#### Sub-Issue Tools | ||
| 139 | +| Tool Name | Prefixed Name | Description | | ||
| 140 | +|-----------|---------------|-------------| | ||
| 141 | +| `sub_issue_write` | `github_sub_issue_write` | Add/remove/reprioritize sub-issues | | ||
| 142 | + | ||
| 143 | +#### Secret Scanning | ||
| 144 | +| Tool Name | Prefixed Name | Description | | ||
| 145 | +|-----------|---------------|-------------| | ||
| 146 | +| `run_secret_scanning` | `github_run_secret_scanning` | Run secret scanning | | ||
| 147 | + | ||
| 148 | +## Tool Name Prefixing | ||
| 149 | + | ||
| 150 | +All MCP tools are prefixed with their server name to avoid naming collisions. The format is: | ||
| 151 | + | ||
| 152 | +``` | ||
| 153 | +{server_name}_{original_tool_name} | ||
| 154 | +``` | ||
| 155 | + | ||
| 156 | +For example: | ||
| 157 | +- Server: `wikipedia`, Tool: `search_wikipedia` → Prefixed: `wikipedia_search_wikipedia` | ||
| 158 | +- Server: `github`, Tool: `list_repositories` → Prefixed: `github_list_repositories` | ||
| 159 | +- Server: `context7`, Tool: `resolve-library-id` → Prefixed: `context7_resolve_library_id` (note: hyphens become underscores) | ||
| 160 | + | ||
| 161 | +## Tool Name Sanitization | ||
| 162 | + | ||
| 163 | +Tool names are sanitized to be valid Go identifiers: | ||
| 164 | + | ||
| 165 | +1. Non-alphanumeric characters are replaced with underscores (`_`) | ||
| 166 | +2. Leading/trailing underscores are removed | ||
| 167 | +3. Multiple consecutive underscores are collapsed to one | ||
| 168 | +4. If the result is empty, it becomes `"tool"` | ||
| 169 | + | ||
| 170 | +Examples: | ||
| 171 | +- `resolve-library-id` → `resolve_library_id` | ||
| 172 | +- `query-docs` → `query_docs` | ||
| 173 | +- `add-comment_to-pending-review` → `add_comment_to_pending_review` | ||
| 174 | + | ||
| 175 | +## Required Parameters | ||
| 176 | + | ||
| 177 | +Each tool has its own input schema defining required and optional parameters. Required parameters must be provided when calling the tool. | ||
| 178 | + | ||
| 179 | +To see the required parameters for a specific tool, use: | ||
| 180 | +``` | ||
| 181 | +Use the {tool_name} tool | ||
| 182 | +``` | ||
| 183 | + | ||
| 184 | +Mini-me will show you the tool description including required parameters. | ||
| 185 | + | ||
| 186 | +## Tool Filtering | ||
| 187 | + | ||
| 188 | +You can filter which tools from a server are exposed by specifying a `tools` list in the server configuration: | ||
| 189 | + | ||
| 190 | +```yaml | ||
| 191 | +servers: | ||
| 192 | + - name: github | ||
| 193 | + transport: http | ||
| 194 | + url: https://api.githubcopilot.com/mcp/ | ||
| 195 | + auth: | ||
| 196 | + type: bearer | ||
| 197 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 198 | + tools: # Only these tools will be available | ||
| 199 | + - get_me | ||
| 200 | + - list_repositories | ||
| 201 | + - search_code | ||
| 202 | +``` | ||
| 203 | + | ||
| 204 | +## Binary Tool Filtering | ||
| 205 | + | ||
| 206 | +Mini-me **only supports text input/output** for MCP tools. Any tool that requires binary data (images, audio, etc.) is automatically filtered out and a warning is displayed at startup. | ||
| 207 | + | ||
| 208 | +This is a safety measure to ensure compatibility with mini-me's text-only architecture. | ||
| 209 | + | ||
| 210 | +## Tool Execution | ||
| 211 | + | ||
| 212 | +When you use an MCP tool: | ||
| 213 | + | ||
| 214 | +1. **Permission check:** If permissions are set to `ask`, you'll be prompted to confirm | ||
| 215 | +2. **Input validation:** Required parameters are validated | ||
| 216 | +3. **Timeout:** The server's configured timeout applies | ||
| 217 | +4. **Output:** Results are returned as text (non-text content is skipped) | ||
| 218 | +5. **Truncation:** Output may be truncated if it exceeds `maxOutput` characters | ||
| 219 | + | ||
| 220 | +## See Also | ||
| 221 | + | ||
| 222 | +- [MCP Configuration](configuration.md) | ||
| 223 | +- [MCP Architecture](explanation/architecture.md) | ||
added
docs/en/reference/mcp-configuration.md +126 -0 | new file mode 100644 | ||
| @@ -0,0 +1,126 @@ | ||
| 1 | +# Reference: configuration file | |
| 2 | + | |
| 3 | +> Neutral, exhaustive description of the YAML file read by `mm`. Every key is optional; a key left out keeps its built-in default. The file is decoded on top of the defaults, so unknown keys are ignored. | |
| 4 | + | |
| 5 | +## Top-level keys | |
| 6 | + | |
| 7 | +| Key | Type | Default | Description | | |
| 8 | +|-----|------|---------|-------------| | |
| 9 | +| `provider` | string | `dmr` | Registry key of the LLM server: `dmr` or `llamacpp`. Overridden by `AGENT_PROVIDER`. | | |
| 10 | +| `model` | string | `huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M` | Model identifier in the provider's naming. Overridden by `AGENT_MODEL`. | | |
| 11 | +| `baseUrl` | string | `""` (provider default) | OpenAI-compatible endpoint tried first. Overridden by `AGENT_BASE_URL`. | | |
| 12 | +| `fallback` | string or absent | absent (provider default) | Endpoint used when `baseUrl` does not answer `/models` within 2 s. Absent = provider default; `""` = no fallback. | | |
| 13 | +| `apiKeyEnv` | string | `""` (provider default) | Name of the environment variable holding the API key. | | |
| 14 | +| `contextWindow` | int | `0` | Tokens the server serves. `0` = ask the server, else unknown. Must be ≥ 0. | | |
| 15 | +| `maxOutput` | int | `16000` | Maximum characters a tool returns to the model; longer output keeps its head and up to 2000 characters of tail. Must be > 0. | | |
| 16 | +| `maxTurns` | int | `10` | Maximum model ↔ tools round trips per question. Must be > 0. | | |
| 17 | +| `previewLines` | int | `20` | Lines of a command's output echoed to the terminal. `0` disables the echo. Must be ≥ 0. | | |
| 18 | +| `displayCommands` | bool | `false` | Print the numbered list of commands and file operations after each answer. | | |
| 19 | +| `showThinking` | bool | `true` | Show what a thinking model reasons about before it answers. Only the display is optional: the reasoning is always read off the wire, because it is what keeps the watchdog alive while the model thinks. See [Thinking models](../explanation/thinking-models.md). | | |
| 20 | +| `system` | string | built-in prompt | The system prompt, first message of every history. | | |
| 21 | +| `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. | | |
| 22 | +| `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. | | |
| 23 | +| `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. | | |
| 24 | +| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. | | |
| 25 | +| `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. | | |
| 26 | +| `context` | map | see below | Automatic history compression. | | |
| 27 | +| `sessions` | map | see below | Persistent, per-project sessions. | | |
| 28 | +| `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. | | |
| 29 | +| `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. | | |
| 30 | +| `permissions` | string | `auto` | `ask`: in the window mode, `bash`, `write_file` and `edit_file` wait for the user's answer (`a`/`s`/`r`) before running. `auto`: tools run at once. The line mode and the ACP façade do not read it (ACP has its own dialog). Must be `auto` or `ask`. | | |
| 31 | + | |
| 32 | +## `context` keys | |
| 33 | + | |
| 34 | +| Key | Type | Default | Description | | |
| 35 | +|-----|------|---------|-------------| | |
| 36 | +| `enabled` | bool | `false` | Turn the automatic compression on. `/compact` works regardless. | | |
| 37 | +| `threshold` | int | `75` | Percentage of the context window beyond which the history is compressed before the next question. Between 1 and 100. | | |
| 38 | +| `maxMessages` | int | `80` | Message count that triggers a compression when the window is unknown or the estimate is low. `0` disables. Must be ≥ 0. | | |
| 39 | +| `keepLastTurns` | int | `3` | Question turns kept raw at the end of the history. Must be ≥ 1. | | |
| 40 | +| `summaryMaxTokens` | int | `1200` | `max_tokens` of the summary request. Must be > 0. | | |
| 41 | +| `prompt` | string | `""` | Replaces the built-in summary prompt when not empty. | | |
| 42 | +| `showStats` | bool | `true` | Print the one-line `🗜️` report after each compression. | | |
| 43 | + | |
| 44 | +## `sessions` keys | |
| 45 | + | |
| 46 | +| Key | Type | Default | Description | | |
| 47 | +|-----|------|---------|-------------| | |
| 48 | +| `sessions.enabled` | bool | `false` | Save every conversation to a file, rewritten after each turn, `/compact` and `/new`. The file also records, outside the history, every error shown (`errors` array: failed generation, failed compaction, failed `/resume`). Enables `/sessions` and `/resume` in the terminal and `session/load` over ACP. | | |
| 49 | +| `sessions.dir` | string | `.mm/sessions` | Directory of the session files, one `<id>.json` each. Relative to the directory `mm` is started from (terminal) or to the session's `cwd` (ACP); an absolute path is used as-is. Must not be empty. | | |
| 50 | + | |
| 51 | +## `lsp` keys | |
| 52 | + | |
| 53 | +| Key | Type | Default | Description | | |
| 54 | +|-----|------|---------|-------------| | |
| 55 | +| `lsp.servers` | list | `[]` | One entry per language server. The file extension picks the server. | | |
| 56 | +| `lsp.servers[].name` | string | — | How the banner, the tool description and the warnings call it. Required. | | |
| 57 | +| `lsp.servers[].command` | string | — | The executable, looked up on the `PATH` at start-up. Required. Not found: warning, server skipped. | | |
| 58 | +| `lsp.servers[].args` | list of strings | `[]` | Its arguments (many servers need `--stdio`). | | |
| 59 | +| `lsp.servers[].extensions` | list of strings | — | File extensions it serves, with the dot (`.go`, `.ts`). Required, at least one; matched case-insensitively; an extension may be served by one server only. | | |
| 60 | + | |
| 61 | +```yaml | |
| 62 | +lsp: | |
| 63 | + servers: | |
| 64 | + - name: gopls | |
| 65 | + command: gopls | |
| 66 | + extensions: [".go"] | |
| 67 | + - name: typescript | |
| 68 | + command: typescript-language-server | |
| 69 | + args: ["--stdio"] | |
| 70 | + extensions: [".ts", ".tsx", ".js", ".jsx"] | |
| 71 | +``` | |
| 72 | + | |
| 73 | +The shipped `agent.lsp.yaml` is `agent.dmr.yaml` with this block on (gopls active, other servers ready to uncomment) and a system prompt that says when to use the tool. | |
| 74 | + | |
| 75 | +## Provider defaults | |
| 76 | + | |
| 77 | +| Provider | `baseUrl` | `fallback` | `apiKeyEnv` | Key required | | |
| 78 | +|----------|-----------|------------|-------------|--------------| | |
| 79 | +| `dmr` | `http://localhost:12434/engines/v1` | `http://host.docker.internal:12434/engines/v1` | none | no | | |
| 80 | +| `llamacpp` | `http://127.0.0.1:8080/v1` | none | `LLAMA_API_KEY` | no | | |
| 81 | + | |
| 82 | +## Validation errors | |
| 83 | + | |
| 84 | +Reported as `[config error: <path>: <message>]`, exit code 1. | |
| 85 | + | |
| 86 | +| Message | Cause | | |
| 87 | +|---------|-------| | |
| 88 | +| `provider must not be empty` | `provider: ""` | | |
| 89 | +| `model must not be empty` | `model: ""` | | |
| 90 | +| `contextWindow must be >= 0` | negative value | | |
| 91 | +| `maxOutput must be > 0` | zero or negative | | |
| 92 | +| `maxTurns must be > 0` | zero or negative | | |
| 93 | +| `previewLines must be >= 0` | negative value | | |
| 94 | +| `context.threshold must be between 1 and 100` | out of range | | |
| 95 | +| `context.keepLastTurns must be >= 1` | zero or negative | | |
| 96 | +| `context.summaryMaxTokens must be > 0` | zero or negative | | |
| 97 | +| `context.maxMessages must be >= 0` | negative value | | |
| 98 | +| `sessions.dir must not be empty` | empty string | | |
| 99 | +| `lsp.servers[N].name must not be empty` | a server without `name` | | |
| 100 | +| `lsp.servers[N].command must not be empty` | a server without `command` | | |
| 101 | +| `lsp.servers[N].extensions must list at least one extension` | a server without `extensions` | | |
| 102 | +| `lsp.servers[N].extensions: "go" must start with a dot, like ".go"` | an extension without its dot | | |
| 103 | +| `lsp.servers[N]: extension ".ts" is already served by "typescript"` | two servers on one extension | | |
| 104 | +| `permissions must be "auto" or "ask"` | any other value | | |
| 105 | +| `unknown provider "x" (known: dmr, llamacpp)` | Reported by the engine, as `[engine error: …]`. | | |
| 106 | + | |
| 107 | +A start-up warning, not an error, is printed when `context.enabled` is `true`, the window is unknown and `context.maxMessages` is `0`. | |
| 108 | + | |
| 109 | +## Shipped files | |
| 110 | + | |
| 111 | +| File | Purpose | | |
| 112 | +|------|---------| | |
| 113 | +| `agent.yaml` | Docker Model Runner set-up: `provider: dmr`, `maxTurns: 40`, `displayCommands: true`, compression off, a system prompt named "Bob" that describes the skills and file-editing rules. | | |
| 114 | +| `agent.llamacpp.yaml` | llama.cpp set-up: `provider: llamacpp`, `baseUrl: http://127.0.0.1:8080/v1`, `fallback: ""`, compression on. | | |
| 115 | + | |
| 116 | +### Example | |
| 117 | + | |
| 118 | +```yaml | |
| 119 | +provider: llamacpp | |
| 120 | +model: my-alias | |
| 121 | +baseUrl: http://127.0.0.1:8080/v1 | |
| 122 | +fallback: "" | |
| 123 | +maxTurns: 40 | |
| 124 | +context: | |
| 125 | + enabled: true | |
| 126 | +``` | |
| new file mode 100644 | |||
| @@ -0,0 +1,126 @@ | |||
| 1 | +# Reference: configuration file | ||
| 2 | + | ||
| 3 | +> Neutral, exhaustive description of the YAML file read by `mm`. Every key is optional; a key left out keeps its built-in default. The file is decoded on top of the defaults, so unknown keys are ignored. | ||
| 4 | + | ||
| 5 | +## Top-level keys | ||
| 6 | + | ||
| 7 | +| Key | Type | Default | Description | | ||
| 8 | +|-----|------|---------|-------------| | ||
| 9 | +| `provider` | string | `dmr` | Registry key of the LLM server: `dmr` or `llamacpp`. Overridden by `AGENT_PROVIDER`. | | ||
| 10 | +| `model` | string | `huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M` | Model identifier in the provider's naming. Overridden by `AGENT_MODEL`. | | ||
| 11 | +| `baseUrl` | string | `""` (provider default) | OpenAI-compatible endpoint tried first. Overridden by `AGENT_BASE_URL`. | | ||
| 12 | +| `fallback` | string or absent | absent (provider default) | Endpoint used when `baseUrl` does not answer `/models` within 2 s. Absent = provider default; `""` = no fallback. | | ||
| 13 | +| `apiKeyEnv` | string | `""` (provider default) | Name of the environment variable holding the API key. | | ||
| 14 | +| `contextWindow` | int | `0` | Tokens the server serves. `0` = ask the server, else unknown. Must be ≥ 0. | | ||
| 15 | +| `maxOutput` | int | `16000` | Maximum characters a tool returns to the model; longer output keeps its head and up to 2000 characters of tail. Must be > 0. | | ||
| 16 | +| `maxTurns` | int | `10` | Maximum model ↔ tools round trips per question. Must be > 0. | | ||
| 17 | +| `previewLines` | int | `20` | Lines of a command's output echoed to the terminal. `0` disables the echo. Must be ≥ 0. | | ||
| 18 | +| `displayCommands` | bool | `false` | Print the numbered list of commands and file operations after each answer. | | ||
| 19 | +| `showThinking` | bool | `true` | Show what a thinking model reasons about before it answers. Only the display is optional: the reasoning is always read off the wire, because it is what keeps the watchdog alive while the model thinks. See [Thinking models](../explanation/thinking-models.md). | | ||
| 20 | +| `system` | string | built-in prompt | The system prompt, first message of every history. | | ||
| 21 | +| `skillsDir` | string | `skills` | Directory holding the skills, as `<name>.md` files or `<name>/SKILL.md` directories. A relative path is resolved from the directory `mm` is started in (terminal mode, or ACP mode without a config file), or next to the config file (ACP mode with one); the banner warns with the absolute path when nothing is found there. | | ||
| 22 | +| `bashTool` | bool | `true` | Declare `bash`. `false` removes it: the model can still read and edit files (`editTools`) and load skills, but never runs a command. Reword a system prompt that still announces a bash tool. | | ||
| 23 | +| `editTools` | bool | `true` | Declare `read_file`, `write_file`, `edit_file`. `false` leaves `bash` and `read_skill` only. | | ||
| 24 | +| `sampling` | map | `temperature: 0.0`, `top_p: 0.9`, `max_tokens: 4096` | Generation settings, OpenAI API key names, passed to the server. The shipped `agent.yaml` adds `parallel_tool_calls: false`. | | ||
| 25 | +| `watchdogTimeout` | duration | `20s` | Silence tolerated between two streamed tokens before the connection is declared hung. Doubled for summary requests. | | ||
| 26 | +| `context` | map | see below | Automatic history compression. | | ||
| 27 | +| `sessions` | map | see below | Persistent, per-project sessions. | | ||
| 28 | +| `lsp` | map | see below | Language servers behind the `lsp` tool. No server listed = no tool. | | ||
| 29 | +| `tui` | bool | `false` | Start in the single-window mode without the `-tui` flag. Falls back to the line mode when stdin or stdout is not a terminal; ignored with `-acp`. | | ||
| 30 | +| `permissions` | string | `auto` | `ask`: in the window mode, `bash`, `write_file` and `edit_file` wait for the user's answer (`a`/`s`/`r`) before running. `auto`: tools run at once. The line mode and the ACP façade do not read it (ACP has its own dialog). Must be `auto` or `ask`. | | ||
| 31 | + | ||
| 32 | +## `context` keys | ||
| 33 | + | ||
| 34 | +| Key | Type | Default | Description | | ||
| 35 | +|-----|------|---------|-------------| | ||
| 36 | +| `enabled` | bool | `false` | Turn the automatic compression on. `/compact` works regardless. | | ||
| 37 | +| `threshold` | int | `75` | Percentage of the context window beyond which the history is compressed before the next question. Between 1 and 100. | | ||
| 38 | +| `maxMessages` | int | `80` | Message count that triggers a compression when the window is unknown or the estimate is low. `0` disables. Must be ≥ 0. | | ||
| 39 | +| `keepLastTurns` | int | `3` | Question turns kept raw at the end of the history. Must be ≥ 1. | | ||
| 40 | +| `summaryMaxTokens` | int | `1200` | `max_tokens` of the summary request. Must be > 0. | | ||
| 41 | +| `prompt` | string | `""` | Replaces the built-in summary prompt when not empty. | | ||
| 42 | +| `showStats` | bool | `true` | Print the one-line `🗜️` report after each compression. | | ||
| 43 | + | ||
| 44 | +## `sessions` keys | ||
| 45 | + | ||
| 46 | +| Key | Type | Default | Description | | ||
| 47 | +|-----|------|---------|-------------| | ||
| 48 | +| `sessions.enabled` | bool | `false` | Save every conversation to a file, rewritten after each turn, `/compact` and `/new`. The file also records, outside the history, every error shown (`errors` array: failed generation, failed compaction, failed `/resume`). Enables `/sessions` and `/resume` in the terminal and `session/load` over ACP. | | ||
| 49 | +| `sessions.dir` | string | `.mm/sessions` | Directory of the session files, one `<id>.json` each. Relative to the directory `mm` is started from (terminal) or to the session's `cwd` (ACP); an absolute path is used as-is. Must not be empty. | | ||
| 50 | + | ||
| 51 | +## `lsp` keys | ||
| 52 | + | ||
| 53 | +| Key | Type | Default | Description | | ||
| 54 | +|-----|------|---------|-------------| | ||
| 55 | +| `lsp.servers` | list | `[]` | One entry per language server. The file extension picks the server. | | ||
| 56 | +| `lsp.servers[].name` | string | — | How the banner, the tool description and the warnings call it. Required. | | ||
| 57 | +| `lsp.servers[].command` | string | — | The executable, looked up on the `PATH` at start-up. Required. Not found: warning, server skipped. | | ||
| 58 | +| `lsp.servers[].args` | list of strings | `[]` | Its arguments (many servers need `--stdio`). | | ||
| 59 | +| `lsp.servers[].extensions` | list of strings | — | File extensions it serves, with the dot (`.go`, `.ts`). Required, at least one; matched case-insensitively; an extension may be served by one server only. | | ||
| 60 | + | ||
| 61 | +```yaml | ||
| 62 | +lsp: | ||
| 63 | + servers: | ||
| 64 | + - name: gopls | ||
| 65 | + command: gopls | ||
| 66 | + extensions: [".go"] | ||
| 67 | + - name: typescript | ||
| 68 | + command: typescript-language-server | ||
| 69 | + args: ["--stdio"] | ||
| 70 | + extensions: [".ts", ".tsx", ".js", ".jsx"] | ||
| 71 | +``` | ||
| 72 | + | ||
| 73 | +The shipped `agent.lsp.yaml` is `agent.dmr.yaml` with this block on (gopls active, other servers ready to uncomment) and a system prompt that says when to use the tool. | ||
| 74 | + | ||
| 75 | +## Provider defaults | ||
| 76 | + | ||
| 77 | +| Provider | `baseUrl` | `fallback` | `apiKeyEnv` | Key required | | ||
| 78 | +|----------|-----------|------------|-------------|--------------| | ||
| 79 | +| `dmr` | `http://localhost:12434/engines/v1` | `http://host.docker.internal:12434/engines/v1` | none | no | | ||
| 80 | +| `llamacpp` | `http://127.0.0.1:8080/v1` | none | `LLAMA_API_KEY` | no | | ||
| 81 | + | ||
| 82 | +## Validation errors | ||
| 83 | + | ||
| 84 | +Reported as `[config error: <path>: <message>]`, exit code 1. | ||
| 85 | + | ||
| 86 | +| Message | Cause | | ||
| 87 | +|---------|-------| | ||
| 88 | +| `provider must not be empty` | `provider: ""` | | ||
| 89 | +| `model must not be empty` | `model: ""` | | ||
| 90 | +| `contextWindow must be >= 0` | negative value | | ||
| 91 | +| `maxOutput must be > 0` | zero or negative | | ||
| 92 | +| `maxTurns must be > 0` | zero or negative | | ||
| 93 | +| `previewLines must be >= 0` | negative value | | ||
| 94 | +| `context.threshold must be between 1 and 100` | out of range | | ||
| 95 | +| `context.keepLastTurns must be >= 1` | zero or negative | | ||
| 96 | +| `context.summaryMaxTokens must be > 0` | zero or negative | | ||
| 97 | +| `context.maxMessages must be >= 0` | negative value | | ||
| 98 | +| `sessions.dir must not be empty` | empty string | | ||
| 99 | +| `lsp.servers[N].name must not be empty` | a server without `name` | | ||
| 100 | +| `lsp.servers[N].command must not be empty` | a server without `command` | | ||
| 101 | +| `lsp.servers[N].extensions must list at least one extension` | a server without `extensions` | | ||
| 102 | +| `lsp.servers[N].extensions: "go" must start with a dot, like ".go"` | an extension without its dot | | ||
| 103 | +| `lsp.servers[N]: extension ".ts" is already served by "typescript"` | two servers on one extension | | ||
| 104 | +| `permissions must be "auto" or "ask"` | any other value | | ||
| 105 | +| `unknown provider "x" (known: dmr, llamacpp)` | Reported by the engine, as `[engine error: …]`. | | ||
| 106 | + | ||
| 107 | +A start-up warning, not an error, is printed when `context.enabled` is `true`, the window is unknown and `context.maxMessages` is `0`. | ||
| 108 | + | ||
| 109 | +## Shipped files | ||
| 110 | + | ||
| 111 | +| File | Purpose | | ||
| 112 | +|------|---------| | ||
| 113 | +| `agent.yaml` | Docker Model Runner set-up: `provider: dmr`, `maxTurns: 40`, `displayCommands: true`, compression off, a system prompt named "Bob" that describes the skills and file-editing rules. | | ||
| 114 | +| `agent.llamacpp.yaml` | llama.cpp set-up: `provider: llamacpp`, `baseUrl: http://127.0.0.1:8080/v1`, `fallback: ""`, compression on. | | ||
| 115 | + | ||
| 116 | +### Example | ||
| 117 | + | ||
| 118 | +```yaml | ||
| 119 | +provider: llamacpp | ||
| 120 | +model: my-alias | ||
| 121 | +baseUrl: http://127.0.0.1:8080/v1 | ||
| 122 | +fallback: "" | ||
| 123 | +maxTurns: 40 | ||
| 124 | +context: | ||
| 125 | + enabled: true | ||
| 126 | +``` | ||
added
docs/en/tutorials/mcp-getting-started.md +80 -0 | new file mode 100644 | ||
| @@ -0,0 +1,80 @@ | ||
| 1 | +# Tutorial: your first session with mini-me | |
| 2 | + | |
| 3 | +By the end of this tutorial, you will have built the `mm` binary, started it against Docker Model Runner, asked it one question that makes it run a shell command, and left cleanly. No prior knowledge of the project is required. | |
| 4 | + | |
| 5 | +## Prerequisites | |
| 6 | + | |
| 7 | +- Go 1.26 or later on your `PATH` (`go version` answers). | |
| 8 | +- Docker Desktop with Docker Model Runner enabled and reachable on `http://localhost:12434`. | |
| 9 | +- The model named in `agent.yaml` pulled locally. The file shipped with the project names `huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M`; pull it with `docker model pull <that name>`. | |
| 10 | +- A terminal open at the root of the repository (the directory that holds `main.go` and `agent.yaml`). | |
| 11 | + | |
| 12 | +## Step 1 — Build the binary | |
| 13 | + | |
| 14 | +Type: | |
| 15 | + | |
| 16 | +```bash | |
| 17 | +go build -o mm . | |
| 18 | +``` | |
| 19 | + | |
| 20 | +You should see no output at all. A file named `mm` now sits in the current directory. | |
| 21 | + | |
| 22 | +We have just compiled the whole agent into a single executable. | |
| 23 | + | |
| 24 | +## Step 2 — Start the agent | |
| 25 | + | |
| 26 | +Type: | |
| 27 | + | |
| 28 | +```bash | |
| 29 | +./mm | |
| 30 | +``` | |
| 31 | + | |
| 32 | +You should see a banner like this one (the model name is the one from `agent.yaml`): | |
| 33 | + | |
| 34 | +``` | |
| 35 | +mm v0.0.3 | config: agent.yaml | provider: dmr | model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M | ctx: unknown | tools: bash, read_file, write_file, edit_file | skills: 0 | |
| 36 | +Agent ready. Commands: "/quit" to exit, "/abort" to stop current generation, "/compact" to compress the history, "/new" to start a new session, "/sessions" and "/resume" for stored sessions, "/help" to list them. | |
| 37 | +Shortcut: Ctrl+C to abort generation, or Ctrl+C at prompt to quit. | |
| 38 | + | |
| 39 | +> | |
| 40 | +``` | |
| 41 | + | |
| 42 | +If a dimmed `[warning: dmr: nothing answers at …]` line appears above the banner, Docker Model Runner is not running: start it, then run `./mm` again. The agent starts anyway, but the next step would fail. | |
| 43 | + | |
| 44 | +We have just loaded the configuration, connected to the model server, and declared the tools the model may call. | |
| 45 | + | |
| 46 | +## Step 3 — Ask a question that needs a command | |
| 47 | + | |
| 48 | +At the `>` prompt, type: | |
| 49 | + | |
| 50 | +``` | |
| 51 | +list the files in this directory | |
| 52 | +``` | |
| 53 | + | |
| 54 | +You should see, in order: a spinner while the model thinks, a line starting with `🛠️ bash:` showing the command the model chose (typically `ls` or `ls -la`), the first lines of that command's output indented and dimmed, then the model's answer in plain text. The turn ends with a dimmed recap such as: | |
| 55 | + | |
| 56 | +``` | |
| 57 | +⚙ 1 command(s) | |
| 58 | +``` | |
| 59 | + | |
| 60 | +followed by the numbered list of the commands that ran, in green. | |
| 61 | + | |
| 62 | +We have just watched the agent loop once: the model asked for a tool, the tool ran, its output went back to the model, and the model answered. | |
| 63 | + | |
| 64 | +## Step 4 — Leave | |
| 65 | + | |
| 66 | +At the `>` prompt, type: | |
| 67 | + | |
| 68 | +``` | |
| 69 | +/quit | |
| 70 | +``` | |
| 71 | + | |
| 72 | +The program exits. Pressing `Ctrl+C` at the prompt does the same and prints `Goodbye.` first. | |
| 73 | + | |
| 74 | +## What now? | |
| 75 | + | |
| 76 | +You have built `mm`, run one tool-calling turn, and exited. To go further: | |
| 77 | + | |
| 78 | +- To run it on llama.cpp, host it in an editor, or add skills → see the [how-to guides](../how-to/) | |
| 79 | +- To look up a flag, a YAML key or a slash command → see the [reference](../reference/) | |
| 80 | +- To understand how the loop and the two front ends fit together → see the [explanation](../explanation/) | |
| new file mode 100644 | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | +# Tutorial: your first session with mini-me | ||
| 2 | + | ||
| 3 | +By the end of this tutorial, you will have built the `mm` binary, started it against Docker Model Runner, asked it one question that makes it run a shell command, and left cleanly. No prior knowledge of the project is required. | ||
| 4 | + | ||
| 5 | +## Prerequisites | ||
| 6 | + | ||
| 7 | +- Go 1.26 or later on your `PATH` (`go version` answers). | ||
| 8 | +- Docker Desktop with Docker Model Runner enabled and reachable on `http://localhost:12434`. | ||
| 9 | +- The model named in `agent.yaml` pulled locally. The file shipped with the project names `huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M`; pull it with `docker model pull <that name>`. | ||
| 10 | +- A terminal open at the root of the repository (the directory that holds `main.go` and `agent.yaml`). | ||
| 11 | + | ||
| 12 | +## Step 1 — Build the binary | ||
| 13 | + | ||
| 14 | +Type: | ||
| 15 | + | ||
| 16 | +```bash | ||
| 17 | +go build -o mm . | ||
| 18 | +``` | ||
| 19 | + | ||
| 20 | +You should see no output at all. A file named `mm` now sits in the current directory. | ||
| 21 | + | ||
| 22 | +We have just compiled the whole agent into a single executable. | ||
| 23 | + | ||
| 24 | +## Step 2 — Start the agent | ||
| 25 | + | ||
| 26 | +Type: | ||
| 27 | + | ||
| 28 | +```bash | ||
| 29 | +./mm | ||
| 30 | +``` | ||
| 31 | + | ||
| 32 | +You should see a banner like this one (the model name is the one from `agent.yaml`): | ||
| 33 | + | ||
| 34 | +``` | ||
| 35 | +mm v0.0.3 | config: agent.yaml | provider: dmr | model: huggingface.co/jetbrains/mellum2-12b-a2.5b-instruct-gguf-q4_k_m:Q4_K_M | ctx: unknown | tools: bash, read_file, write_file, edit_file | skills: 0 | ||
| 36 | +Agent ready. Commands: "/quit" to exit, "/abort" to stop current generation, "/compact" to compress the history, "/new" to start a new session, "/sessions" and "/resume" for stored sessions, "/help" to list them. | ||
| 37 | +Shortcut: Ctrl+C to abort generation, or Ctrl+C at prompt to quit. | ||
| 38 | + | ||
| 39 | +> | ||
| 40 | +``` | ||
| 41 | + | ||
| 42 | +If a dimmed `[warning: dmr: nothing answers at …]` line appears above the banner, Docker Model Runner is not running: start it, then run `./mm` again. The agent starts anyway, but the next step would fail. | ||
| 43 | + | ||
| 44 | +We have just loaded the configuration, connected to the model server, and declared the tools the model may call. | ||
| 45 | + | ||
| 46 | +## Step 3 — Ask a question that needs a command | ||
| 47 | + | ||
| 48 | +At the `>` prompt, type: | ||
| 49 | + | ||
| 50 | +``` | ||
| 51 | +list the files in this directory | ||
| 52 | +``` | ||
| 53 | + | ||
| 54 | +You should see, in order: a spinner while the model thinks, a line starting with `🛠️ bash:` showing the command the model chose (typically `ls` or `ls -la`), the first lines of that command's output indented and dimmed, then the model's answer in plain text. The turn ends with a dimmed recap such as: | ||
| 55 | + | ||
| 56 | +``` | ||
| 57 | +⚙ 1 command(s) | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +followed by the numbered list of the commands that ran, in green. | ||
| 61 | + | ||
| 62 | +We have just watched the agent loop once: the model asked for a tool, the tool ran, its output went back to the model, and the model answered. | ||
| 63 | + | ||
| 64 | +## Step 4 — Leave | ||
| 65 | + | ||
| 66 | +At the `>` prompt, type: | ||
| 67 | + | ||
| 68 | +``` | ||
| 69 | +/quit | ||
| 70 | +``` | ||
| 71 | + | ||
| 72 | +The program exits. Pressing `Ctrl+C` at the prompt does the same and prints `Goodbye.` first. | ||
| 73 | + | ||
| 74 | +## What now? | ||
| 75 | + | ||
| 76 | +You have built `mm`, run one tool-calling turn, and exited. To go further: | ||
| 77 | + | ||
| 78 | +- To run it on llama.cpp, host it in an editor, or add skills → see the [how-to guides](../how-to/) | ||
| 79 | +- To look up a flag, a YAML key or a slash command → see the [reference](../reference/) | ||
| 80 | +- To understand how the loop and the two front ends fit together → see the [explanation](../explanation/) | ||
modified
docs/fr/README.md +14 -0 | @@ -23,6 +23,11 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | ||
| 23 | 23 | - [Naviguer et refactorer le code avec un serveur de langage (`lsp`)](how-to/navigate-code-with-lsp.md) |
| 24 | 24 | - [Exécuter une invite unique et quitter (`-p`/`--prompt`)](how-to/executer-une-invite-unique.md) |
| 25 | 25 | |
| 26 | +### Guides pratiques MCP (Model Context Protocol) | |
| 27 | +- [Utiliser Context7 pour la documentation](how-to/mcp-utiliser-context7.md) | |
| 28 | +- [Utiliser Wikipedia pour la recherche](how-to/mcp-utiliser-wikipedia.md) | |
| 29 | +- [Utiliser GitHub pour l'accès au code](how-to/mcp-utiliser-github.md) | |
| 30 | + | |
| 26 | 31 | ## Référence — consulter |
| 27 | 32 | |
| 28 | 33 | - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage |
| @@ -32,6 +37,11 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | ||
| 32 | 37 | - [Format d'un fichier de skill](reference/skill-format.md) |
| 33 | 38 | - [Surface Agent Client Protocol](reference/acp.md) |
| 34 | 39 | |
| 40 | +### Référence MCP (Model Context Protocol) | |
| 41 | +- [Configuration MCP](reference/mcp-configuration.md) — Toutes les options de configuration spécifiques à MCP | |
| 42 | +- [Outils disponibles](reference/mcp-outils-disponibles.md) — Liste complète des outils par serveur MCP | |
| 43 | +- [Authentification](reference/mcp-authentification.md) — Méthodes d'authentification prises en charge pour les serveurs MCP | |
| 44 | + | |
| 35 | 45 | ## Explication — comprendre |
| 36 | 46 | |
| 37 | 47 | - [Architecture : une boucle, trois façades](explanation/architecture.md) |
| @@ -42,6 +52,10 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | ||
| 42 | 52 | - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie |
| 43 | 53 | - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
| 44 | 54 | |
| 55 | +### Explication MCP (Model Context Protocol) | |
| 56 | +- [Architecture MCP](explanation/mcp-architecture.md) — Comment MCP est intégré à mini-me | |
| 57 | +- [Décisions de conception](explanation/mcp-decisions.md) — Pourquoi ces choix techniques pour MCP ? | |
| 58 | + | |
| 45 | 59 | ## Périmètre |
| 46 | 60 | |
| 47 | 61 | Cette documentation couvre le binaire `mm` et les paquets sous `internal/`. Les répertoires `kits/` et `tmp/` à la racine du dépôt ne font pas partie du projet et ne sont pas documentés ici. |
| @@ -23,6 +23,11 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | |||
| 23 | - [Naviguer et refactorer le code avec un serveur de langage (`lsp`)](how-to/navigate-code-with-lsp.md) | 23 | - [Naviguer et refactorer le code avec un serveur de langage (`lsp`)](how-to/navigate-code-with-lsp.md) |
| 24 | - [Exécuter une invite unique et quitter (`-p`/`--prompt`)](how-to/executer-une-invite-unique.md) | 24 | - [Exécuter une invite unique et quitter (`-p`/`--prompt`)](how-to/executer-une-invite-unique.md) |
| 25 | 25 | ||
| 26 | +### Guides pratiques MCP (Model Context Protocol) | ||
| 27 | +- [Utiliser Context7 pour la documentation](how-to/mcp-utiliser-context7.md) | ||
| 28 | +- [Utiliser Wikipedia pour la recherche](how-to/mcp-utiliser-wikipedia.md) | ||
| 29 | +- [Utiliser GitHub pour l'accès au code](how-to/mcp-utiliser-github.md) | ||
| 30 | + | ||
| 26 | ## Référence — consulter | 31 | ## Référence — consulter |
| 27 | 32 | ||
| 28 | - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage | 33 | - [Ligne de commande](reference/cli.md) — options, variables d'environnement, bannière de démarrage |
| @@ -32,6 +37,11 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | |||
| 32 | - [Format d'un fichier de skill](reference/skill-format.md) | 37 | - [Format d'un fichier de skill](reference/skill-format.md) |
| 33 | - [Surface Agent Client Protocol](reference/acp.md) | 38 | - [Surface Agent Client Protocol](reference/acp.md) |
| 34 | 39 | ||
| 40 | +### Référence MCP (Model Context Protocol) | ||
| 41 | +- [Configuration MCP](reference/mcp-configuration.md) — Toutes les options de configuration spécifiques à MCP | ||
| 42 | +- [Outils disponibles](reference/mcp-outils-disponibles.md) — Liste complète des outils par serveur MCP | ||
| 43 | +- [Authentification](reference/mcp-authentification.md) — Méthodes d'authentification prises en charge pour les serveurs MCP | ||
| 44 | + | ||
| 35 | ## Explication — comprendre | 45 | ## Explication — comprendre |
| 36 | 46 | ||
| 37 | - [Architecture : une boucle, trois façades](explanation/architecture.md) | 47 | - [Architecture : une boucle, trois façades](explanation/architecture.md) |
| @@ -42,6 +52,10 @@ Cette documentation est organisée selon les quatre quadrants [Diátaxis](https: | |||
| 42 | - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie | 52 | - [Modèles à raisonnement](explanation/thinking-models.md) — d'où vient le raisonnement d'un modèle, pourquoi `mm` le lit sous Genkit, et pourquoi le lire garde le chien de garde en vie |
| 43 | - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) | 53 | - Diagramme des paquets (source draw.io) : [`docs/diagrams/packages.drawio`](../diagrams/packages.drawio) |
| 44 | 54 | ||
| 55 | +### Explication MCP (Model Context Protocol) | ||
| 56 | +- [Architecture MCP](explanation/mcp-architecture.md) — Comment MCP est intégré à mini-me | ||
| 57 | +- [Décisions de conception](explanation/mcp-decisions.md) — Pourquoi ces choix techniques pour MCP ? | ||
| 58 | + | ||
| 45 | ## Périmètre | 59 | ## Périmètre |
| 46 | 60 | ||
| 47 | Cette documentation couvre le binaire `mm` et les paquets sous `internal/`. Les répertoires `kits/` et `tmp/` à la racine du dépôt ne font pas partie du projet et ne sont pas documentés ici. | 61 | Cette documentation couvre le binaire `mm` et les paquets sous `internal/`. Les répertoires `kits/` et `tmp/` à la racine du dépôt ne font pas partie du projet et ne sont pas documentés ici. |
added
docs/fr/explanation/mcp-architecture.md +66 -0 | new file mode 100644 | ||
| @@ -0,0 +1,66 @@ | ||
| 1 | +# Architecture : une boucle, trois façades — explication | |
| 2 | + | |
| 3 | +## De quoi s'agit-il ? | |
| 4 | + | |
| 5 | +`mm` est volontairement petit : une boucle d'agent, une poignée d'outils et un fichier de configuration. Ce qui mérite le regard, c'est la façon dont les pièces sont découpées pour que la même boucle puisse être pilotée depuis un terminal — ligne à ligne ou dans une fenêtre — et depuis un éditeur sans rien dupliquer. | |
| 6 | + | |
| 7 | +Le point d'entrée, `main.go`, ne fait que du câblage : il charge les réglages, ouvre le moteur, construit la liste des outils, affiche la bannière et passe la main à l'une des trois façades. Chaque préoccupation vit dans son propre paquet sous `internal/` : | |
| 8 | + | |
| 9 | +| Paquet | Responsabilité | | |
| 10 | +|--------|----------------| | |
| 11 | +| `config` | Tous les réglages dans une structure, avec des valeurs par défaut intégrées surchargées par le YAML et quelques variables d'environnement. | | |
| 12 | +| `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, le registre des fournisseurs, et le raisonnement qu'un modèle diffuse à côté de sa réponse (voir [Modèles à raisonnement](thinking-models.md)). | | |
| 13 | +| `tools` | Les outils intégrés que le modèle peut appeler. | | |
| 14 | +| `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. | | |
| 15 | +| `lsp` | Le client de serveur de langage derrière l'outil `lsp` : démarre les serveurs configurés au premier usage (clé `lsp.servers`), synchronise les fichiers depuis le disque, demande définition, références, hover, symboles, diagnostics et renommage, et met les réponses en forme `chemin:ligne:colonne <ligne>`. Construit sur `go.lsp.dev/protocol` et `go.lsp.dev/jsonrpc2`. | | |
| 16 | +| `skills` | La découverte des procédures markdown et le rendu du catalogue de `read_skill`. | | |
| 17 | +| `command` | L'unique table des commandes slash : nom, description, façades qui l'offrent, argument éventuel. Le REPL s'y branche, la façade ACP en annonce la tranche Éditeur, le sélecteur `/` de la fenêtre la liste. | | |
| 18 | +| `clipboard` | Met du texte dans le presse-papiers système : un outil natif du `PATH` d'abord (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 en repli, et il dit lequel a servi. Utilisé par `/copy` dans le REPL et la fenêtre. | | |
| 19 | +| `store` | Fichiers de session par projet (`<dir>/<id>.json`, réécriture complète, atomique) : création, sauvegarde, chargement, liste, plus récente. Utilisé par les deux façades quand `sessions.enabled`. | | |
| 20 | +| `mention` | La notation `@chemin` d'une question : les chemins existants deviennent des lignes `[attached file: …]`, la même forme que le `resource_link` d'un éditeur. Utilisé par les deux façades. | | |
| 21 | +| `detector` | La détection d'une même action répétée avec le même résultat. | | |
| 22 | +| `compact` | La compression de l'historique en un résumé écrit par le modèle. | | |
| 23 | +| `version` | Quel `mm` c'est : le tag estampillé à la construction, ou le commit que Go embarque sinon. Lu par `--version`, par la bannière de démarrage et par la barre d'état de la fenêtre. | | |
| 24 | +| `spinner` | L'indicateur « toujours en cours » sur un terminal. | | |
| 25 | +| `ui` | Où va la sortie destinée à l'humain, et le puits d'événements que les façades partagent. | | |
| 26 | +| `agent` | Le REPL du terminal. | | |
| 27 | +| `acp` | La façade Agent Client Protocol. | | |
| 28 | +| `tui` | La façade fenêtre unique (`mm -tui`) : un `ui.Sink` qui dessine sur tcell, une valeur de transcription, une boucle d'événements réveillée par les goroutines du moteur et des outils. | | |
| 29 | + | |
| 30 | +Un diagramme draw.io de ces paquets et de leurs dépendances est conservé dans [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio). | |
| 31 | + | |
| 32 | +## Pourquoi c'est conçu ainsi | |
| 33 | + | |
| 34 | +**Les outils rapportent les échecs comme du texte, jamais comme des erreurs.** Une commande qui sort avec un code non nul, un fichier qui n'existe pas, une édition ambiguë : tout revient au modèle comme sortie de l'outil. C'est le modèle qui doit réagir, donc c'est lui qui doit lire le message. Une erreur Go terminerait le tour à sa place. | |
| 35 | + | |
| 36 | +**L'historique complet est conservé même quand un tour échoue.** Genkit ne renvoie que le dernier message ; le moteur renvoie toute la conversation, appels d'outils et résultats compris. Les façades gardent cet historique après une interruption, une coupure du watchdog ou un dépassement de `maxTurns`, parce que les commandes déjà exécutées sont des faits que le modèle ne doit ni rejouer ni inventer à la question suivante. Seule une question restée sans aucune réponse est retirée. | |
| 37 | + | |
| 38 | +**La sortie est montrée avant que le modèle la voie.** Dans le terminal, `bash` affiche les premières lignes de la sortie d'une commande avant de la renvoyer. « Montre-moi ce fichier » veut dire le montrer ; laisser le modèle le résumer a été observé laissant l'utilisateur sans rien. | |
| 39 | + | |
| 40 | +**Une boucle, trois façades.** Le REPL du terminal, la fenêtre et la façade ACP reçoivent exactement le même moteur, le même prompt système et la même liste d'outils. Ce qui diffère, c'est la destination des événements. Dans le terminal, les outils affichent eux-mêmes leurs lignes `🛠️` et personne ne demande de permission. En mode ACP, stdout appartient à JSON-RPC, donc `main.go` redirige la sortie humaine vers stderr, désactive le spinner, et la façade installe un `ui.Sink` à chaque tour. Les outils vérifient s'il y a un puits actif : s'il y en a un, ils émettent des événements `ToolStart`, `Allow`, `ToolRunning`, `ToolEnd` au lieu d'afficher. Le paquet `ui` ne sait rien du protocole ; `acp` est le seul endroit qui traduit ces événements en notifications `session/update` et en demandes de permission. Le mode fenêtre rejoue le même tour : `tui` installe son propre `ui.Sink` à chaque tour, et les événements deviennent des entrées d'une transcription dessinée à l'écran — les outils n'ont donc rien eu à changer pour gagner une troisième maison, et l'invite de permission en ligne de `permissions: ask` est la version terminal du dialogue de l'éditeur. | |
| 41 | + | |
| 42 | +**Une seule table de commandes.** Les commandes slash vivent dans `internal/command`, marquées des façades qui les offrent. Le REPL et la fenêtre lisent la même table ; la façade ACP n'annonce que la tranche Éditeur (`/new`, `/compact`) via `available_commands_update`, parce que l'éditeur possède le processus et annule lui-même les tours. Une commande ajoutée à un endroit est donc offerte — ou délibérément non — partout à la fois. | |
| 43 | + | |
| 44 | +**La fenêtre dessine depuis une valeur, sur une seule goroutine.** Le moteur diffuse des fragments et les outils émettent des événements depuis leurs propres goroutines ; la fenêtre les enregistre dans une `Transcript` (une valeur protégée par un mutex) et poste un événement de réveil. Seule la boucle d'événements dessine, et elle redessine depuis l'état courant de la transcription à chaque image : un réveil perdu coûte une image en retard, jamais une image perdue. C'est la règle que suit la fenêtre d'agent de turbo-core, et la raison pour laquelle la fenêtre n'a besoin d'aucune bibliothèque de widgets : un peintre, une transcription, une fonction de mise en page et une table de touches suffisent. | |
| 45 | + | |
| 46 | +**La permission vit dans le puits, pas dans les outils.** Un dialogue de permission est la principale chose qu'un éditeur ajoute et que le terminal n'a pas. Placer la politique dans le puits ACP garde les outils identiques dans les deux modes ; « toujours autoriser » est mémorisé par nom d'outil pour la session. | |
| 47 | + | |
| 48 | +**Les tours sont sérialisés en mode ACP.** Le puits actif est global au processus et le moteur n'a jamais été conçu pour des générations concurrentes, donc un second `session/prompt` attend la fin du premier. Le protocole laisse ce choix à l'agent, et les éditeurs n'envoient de toute façon pas de prompts qui se chevauchent. | |
| 49 | + | |
| 50 | +**Le détecteur de boucle est séparé de l'historique.** Il enregistre des triplets outil, entrée, sortie ; le même triplet trois fois de suite fait ajouter par l'outil une instruction de changer d'approche. Il n'est volontairement pas remis à zéro par `/compact` : oublier une conversation ne rend pas nouvelle une commande répétée. Le conseil ne suffit pas à tous les modèles : le 18 septembre 2026, un modèle de 12 milliards de paramètres a répété trente fois le même `edit_file` refusé, `[LOOP_DETECTED]` ajouté à chaque réponse, jusqu'à ce que l'utilisateur appuie sur Échap. Le détecteur a donc un second niveau : trois répétitions signalées après le seuil — six appels identiques — et l'outil renvoie une erreur Go au lieu de texte, la seule exception délibérée à « les outils répondent en texte » : l'agent s'arrête lui-même, il ne rapporte pas un échec à lire par le modèle. Le moteur affiche la phrase du détecteur comme erreur du tour, ne rejoue pas le tour, et le fichier de session l'enregistre. Un troisième garde-fou est dans le flux lui-même : les arguments d'un appel d'outil arrivent fragment par fragment et rien n'en est affiché, donc un modèle qui déraille en composant un appel — `"Greet 2023-06-15 11:16:00 +0000 UTC 2023-06-15 …"` pendant quatre minutes, vu le même jour — ressemble à « rien ne se passe ». Le moteur surveille ces fragments et arrête la génération dès qu'ils répètent un motif ou dépassent un quart de mégaoctet. | |
| 51 | + | |
| 52 | +## Alternatives écartées | |
| 53 | + | |
| 54 | +- **Un binaire par façade.** Écarté parce que le but de l'exercice est de prouver que la boucle est la même ; partager le câblage dans `main.go` est ce qui le rend visible. | |
| 55 | +- **Afficher directement depuis la façade ACP.** Impossible : la spécification interdit tout ce qui n'est pas message de protocole sur stdout. | |
| 56 | +- **Renvoyer des erreurs Go depuis les outils.** Écarté parce que cela termine le tour au lieu de laisser le modèle se rattraper. | |
| 57 | + | |
| 58 | +## Liens avec le reste | |
| 59 | + | |
| 60 | +- Le registre de fournisseurs utilisé par le moteur est décrit dans [fournisseurs](providers.md). | |
| 61 | +- Pourquoi et comment l'historique est raccourci est dans [compression du contexte](context-compression.md). | |
| 62 | +- La surface exacte du protocole est dans la [référence ACP](../reference/acp.md) ; le contrat des outils est dans la [référence des outils](../reference/tools.md). | |
| 63 | + | |
| 64 | +## Carte des paquets | |
| 65 | + | |
| 66 | +Le graphe d'import de tous les paquets, avec une ligne de rôle sur chacun, est tenu sous forme de diagramme draw.io dans [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio) (s'ouvre dans diagrams.net ou l'extension Draw.io de VS Code). Il est regénéré à partir de `go list` chaque fois qu'un paquet est ajouté, retiré ou recâblé. | |
| new file mode 100644 | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | +# Architecture : une boucle, trois façades — explication | ||
| 2 | + | ||
| 3 | +## De quoi s'agit-il ? | ||
| 4 | + | ||
| 5 | +`mm` est volontairement petit : une boucle d'agent, une poignée d'outils et un fichier de configuration. Ce qui mérite le regard, c'est la façon dont les pièces sont découpées pour que la même boucle puisse être pilotée depuis un terminal — ligne à ligne ou dans une fenêtre — et depuis un éditeur sans rien dupliquer. | ||
| 6 | + | ||
| 7 | +Le point d'entrée, `main.go`, ne fait que du câblage : il charge les réglages, ouvre le moteur, construit la liste des outils, affiche la bannière et passe la main à l'une des trois façades. Chaque préoccupation vit dans son propre paquet sous `internal/` : | ||
| 8 | + | ||
| 9 | +| Paquet | Responsabilité | | ||
| 10 | +|--------|----------------| | ||
| 11 | +| `config` | Tous les réglages dans une structure, avec des valeurs par défaut intégrées surchargées par le YAML et quelques variables d'environnement. | | ||
| 12 | +| `engine` | La connexion au serveur de modèle et la génération elle-même : streaming, watchdog, nouvelle tentative sans streaming, requêtes de résumé, le registre des fournisseurs, et le raisonnement qu'un modèle diffuse à côté de sa réponse (voir [Modèles à raisonnement](thinking-models.md)). | | ||
| 13 | +| `tools` | Les outils intégrés que le modèle peut appeler. | | ||
| 14 | +| `fileedit` | L'édition de fichiers par remplacement exact, les règles derrière `edit_file`. | | ||
| 15 | +| `lsp` | Le client de serveur de langage derrière l'outil `lsp` : démarre les serveurs configurés au premier usage (clé `lsp.servers`), synchronise les fichiers depuis le disque, demande définition, références, hover, symboles, diagnostics et renommage, et met les réponses en forme `chemin:ligne:colonne <ligne>`. Construit sur `go.lsp.dev/protocol` et `go.lsp.dev/jsonrpc2`. | | ||
| 16 | +| `skills` | La découverte des procédures markdown et le rendu du catalogue de `read_skill`. | | ||
| 17 | +| `command` | L'unique table des commandes slash : nom, description, façades qui l'offrent, argument éventuel. Le REPL s'y branche, la façade ACP en annonce la tranche Éditeur, le sélecteur `/` de la fenêtre la liste. | | ||
| 18 | +| `clipboard` | Met du texte dans le presse-papiers système : un outil natif du `PATH` d'abord (`pbcopy`, `wl-copy`, `xclip`, `xsel`), OSC 52 en repli, et il dit lequel a servi. Utilisé par `/copy` dans le REPL et la fenêtre. | | ||
| 19 | +| `store` | Fichiers de session par projet (`<dir>/<id>.json`, réécriture complète, atomique) : création, sauvegarde, chargement, liste, plus récente. Utilisé par les deux façades quand `sessions.enabled`. | | ||
| 20 | +| `mention` | La notation `@chemin` d'une question : les chemins existants deviennent des lignes `[attached file: …]`, la même forme que le `resource_link` d'un éditeur. Utilisé par les deux façades. | | ||
| 21 | +| `detector` | La détection d'une même action répétée avec le même résultat. | | ||
| 22 | +| `compact` | La compression de l'historique en un résumé écrit par le modèle. | | ||
| 23 | +| `version` | Quel `mm` c'est : le tag estampillé à la construction, ou le commit que Go embarque sinon. Lu par `--version`, par la bannière de démarrage et par la barre d'état de la fenêtre. | | ||
| 24 | +| `spinner` | L'indicateur « toujours en cours » sur un terminal. | | ||
| 25 | +| `ui` | Où va la sortie destinée à l'humain, et le puits d'événements que les façades partagent. | | ||
| 26 | +| `agent` | Le REPL du terminal. | | ||
| 27 | +| `acp` | La façade Agent Client Protocol. | | ||
| 28 | +| `tui` | La façade fenêtre unique (`mm -tui`) : un `ui.Sink` qui dessine sur tcell, une valeur de transcription, une boucle d'événements réveillée par les goroutines du moteur et des outils. | | ||
| 29 | + | ||
| 30 | +Un diagramme draw.io de ces paquets et de leurs dépendances est conservé dans [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio). | ||
| 31 | + | ||
| 32 | +## Pourquoi c'est conçu ainsi | ||
| 33 | + | ||
| 34 | +**Les outils rapportent les échecs comme du texte, jamais comme des erreurs.** Une commande qui sort avec un code non nul, un fichier qui n'existe pas, une édition ambiguë : tout revient au modèle comme sortie de l'outil. C'est le modèle qui doit réagir, donc c'est lui qui doit lire le message. Une erreur Go terminerait le tour à sa place. | ||
| 35 | + | ||
| 36 | +**L'historique complet est conservé même quand un tour échoue.** Genkit ne renvoie que le dernier message ; le moteur renvoie toute la conversation, appels d'outils et résultats compris. Les façades gardent cet historique après une interruption, une coupure du watchdog ou un dépassement de `maxTurns`, parce que les commandes déjà exécutées sont des faits que le modèle ne doit ni rejouer ni inventer à la question suivante. Seule une question restée sans aucune réponse est retirée. | ||
| 37 | + | ||
| 38 | +**La sortie est montrée avant que le modèle la voie.** Dans le terminal, `bash` affiche les premières lignes de la sortie d'une commande avant de la renvoyer. « Montre-moi ce fichier » veut dire le montrer ; laisser le modèle le résumer a été observé laissant l'utilisateur sans rien. | ||
| 39 | + | ||
| 40 | +**Une boucle, trois façades.** Le REPL du terminal, la fenêtre et la façade ACP reçoivent exactement le même moteur, le même prompt système et la même liste d'outils. Ce qui diffère, c'est la destination des événements. Dans le terminal, les outils affichent eux-mêmes leurs lignes `🛠️` et personne ne demande de permission. En mode ACP, stdout appartient à JSON-RPC, donc `main.go` redirige la sortie humaine vers stderr, désactive le spinner, et la façade installe un `ui.Sink` à chaque tour. Les outils vérifient s'il y a un puits actif : s'il y en a un, ils émettent des événements `ToolStart`, `Allow`, `ToolRunning`, `ToolEnd` au lieu d'afficher. Le paquet `ui` ne sait rien du protocole ; `acp` est le seul endroit qui traduit ces événements en notifications `session/update` et en demandes de permission. Le mode fenêtre rejoue le même tour : `tui` installe son propre `ui.Sink` à chaque tour, et les événements deviennent des entrées d'une transcription dessinée à l'écran — les outils n'ont donc rien eu à changer pour gagner une troisième maison, et l'invite de permission en ligne de `permissions: ask` est la version terminal du dialogue de l'éditeur. | ||
| 41 | + | ||
| 42 | +**Une seule table de commandes.** Les commandes slash vivent dans `internal/command`, marquées des façades qui les offrent. Le REPL et la fenêtre lisent la même table ; la façade ACP n'annonce que la tranche Éditeur (`/new`, `/compact`) via `available_commands_update`, parce que l'éditeur possède le processus et annule lui-même les tours. Une commande ajoutée à un endroit est donc offerte — ou délibérément non — partout à la fois. | ||
| 43 | + | ||
| 44 | +**La fenêtre dessine depuis une valeur, sur une seule goroutine.** Le moteur diffuse des fragments et les outils émettent des événements depuis leurs propres goroutines ; la fenêtre les enregistre dans une `Transcript` (une valeur protégée par un mutex) et poste un événement de réveil. Seule la boucle d'événements dessine, et elle redessine depuis l'état courant de la transcription à chaque image : un réveil perdu coûte une image en retard, jamais une image perdue. C'est la règle que suit la fenêtre d'agent de turbo-core, et la raison pour laquelle la fenêtre n'a besoin d'aucune bibliothèque de widgets : un peintre, une transcription, une fonction de mise en page et une table de touches suffisent. | ||
| 45 | + | ||
| 46 | +**La permission vit dans le puits, pas dans les outils.** Un dialogue de permission est la principale chose qu'un éditeur ajoute et que le terminal n'a pas. Placer la politique dans le puits ACP garde les outils identiques dans les deux modes ; « toujours autoriser » est mémorisé par nom d'outil pour la session. | ||
| 47 | + | ||
| 48 | +**Les tours sont sérialisés en mode ACP.** Le puits actif est global au processus et le moteur n'a jamais été conçu pour des générations concurrentes, donc un second `session/prompt` attend la fin du premier. Le protocole laisse ce choix à l'agent, et les éditeurs n'envoient de toute façon pas de prompts qui se chevauchent. | ||
| 49 | + | ||
| 50 | +**Le détecteur de boucle est séparé de l'historique.** Il enregistre des triplets outil, entrée, sortie ; le même triplet trois fois de suite fait ajouter par l'outil une instruction de changer d'approche. Il n'est volontairement pas remis à zéro par `/compact` : oublier une conversation ne rend pas nouvelle une commande répétée. Le conseil ne suffit pas à tous les modèles : le 18 septembre 2026, un modèle de 12 milliards de paramètres a répété trente fois le même `edit_file` refusé, `[LOOP_DETECTED]` ajouté à chaque réponse, jusqu'à ce que l'utilisateur appuie sur Échap. Le détecteur a donc un second niveau : trois répétitions signalées après le seuil — six appels identiques — et l'outil renvoie une erreur Go au lieu de texte, la seule exception délibérée à « les outils répondent en texte » : l'agent s'arrête lui-même, il ne rapporte pas un échec à lire par le modèle. Le moteur affiche la phrase du détecteur comme erreur du tour, ne rejoue pas le tour, et le fichier de session l'enregistre. Un troisième garde-fou est dans le flux lui-même : les arguments d'un appel d'outil arrivent fragment par fragment et rien n'en est affiché, donc un modèle qui déraille en composant un appel — `"Greet 2023-06-15 11:16:00 +0000 UTC 2023-06-15 …"` pendant quatre minutes, vu le même jour — ressemble à « rien ne se passe ». Le moteur surveille ces fragments et arrête la génération dès qu'ils répètent un motif ou dépassent un quart de mégaoctet. | ||
| 51 | + | ||
| 52 | +## Alternatives écartées | ||
| 53 | + | ||
| 54 | +- **Un binaire par façade.** Écarté parce que le but de l'exercice est de prouver que la boucle est la même ; partager le câblage dans `main.go` est ce qui le rend visible. | ||
| 55 | +- **Afficher directement depuis la façade ACP.** Impossible : la spécification interdit tout ce qui n'est pas message de protocole sur stdout. | ||
| 56 | +- **Renvoyer des erreurs Go depuis les outils.** Écarté parce que cela termine le tour au lieu de laisser le modèle se rattraper. | ||
| 57 | + | ||
| 58 | +## Liens avec le reste | ||
| 59 | + | ||
| 60 | +- Le registre de fournisseurs utilisé par le moteur est décrit dans [fournisseurs](providers.md). | ||
| 61 | +- Pourquoi et comment l'historique est raccourci est dans [compression du contexte](context-compression.md). | ||
| 62 | +- La surface exacte du protocole est dans la [référence ACP](../reference/acp.md) ; le contrat des outils est dans la [référence des outils](../reference/tools.md). | ||
| 63 | + | ||
| 64 | +## Carte des paquets | ||
| 65 | + | ||
| 66 | +Le graphe d'import de tous les paquets, avec une ligne de rôle sur chacun, est tenu sous forme de diagramme draw.io dans [`docs/diagrams/packages.drawio`](../../diagrams/packages.drawio) (s'ouvre dans diagrams.net ou l'extension Draw.io de VS Code). Il est regénéré à partir de `go list` chaque fois qu'un paquet est ajouté, retiré ou recâblé. | ||
added
docs/fr/explanation/mcp-decisions.md +284 -0 | new file mode 100644 | ||
| @@ -0,0 +1,284 @@ | ||
| 1 | +# Décisions de conception MCP | |
| 2 | + | |
| 3 | +Ce document explique les principales décisions de conception prises lors de l'implémentation de la prise en charge MCP (Model Context Protocol) dans mini-me. | |
| 4 | + | |
| 5 | +## Table des matières | |
| 6 | + | |
| 7 | +- [Transport HTTP uniquement](#transport-http-uniquement) | |
| 8 | +- [Prise en charge du texte uniquement](#prise-en-charge-du-texte-uniquement) | |
| 9 | +- [Pas de transport stdio (pour l'instant)](#pas-de-transport-stdio-pour-linstant) | |
| 10 | +- [Pas de prise en charge OAuth 2.0](#pas-de-prise-en-charge-oauth-20) | |
| 11 | +- [Pas de mise en cache](#pas-de-mise-en-cache) | |
| 12 | +- [Pas de découverte paresseuse](#pas-de-découverte-paresseuse) | |
| 13 | +- [Préfixage des noms d'outils](#préfixage-des-noms-doutils) | |
| 14 | +- [Filtrage des outils](#filtrage-des-outils) | |
| 15 | +- [Authentification via variables d'environnement](#authentification-via-variables-denvironnement) | |
| 16 | +- [Système de permissions](#système-de-permissions) | |
| 17 | +- [Gestion des erreurs](#gestion-des-erreurs) | |
| 18 | + | |
| 19 | +--- | |
| 20 | + | |
| 21 | +## Transport HTTP uniquement | |
| 22 | + | |
| 23 | +**Décision :** Implémenter uniquement le transport HTTP (HTTP streamable), pas stdio. | |
| 24 | + | |
| 25 | +**Justification :** | |
| 26 | + | |
| 27 | +1. **Simplicité :** Le transport HTTP est plus simple à implémenter et à déboguer | |
| 28 | +2. **Cas d'utilisation courant :** La plupart des serveurs MCP hébergés utilisent le transport HTTP | |
| 29 | +3. **Sécurité :** HTTP sur TLS fournit une sécurité intégrée | |
| 30 | +4. **Compatibilité :** Fonctionne avec tous les services MCP hébergés (GitHub, Context7, Wikipedia, etc.) | |
| 31 | +5. **Effort d'implémentation :** Stdio nécessite la gestion de sous-processus, ce qui ajoute de la complexité | |
| 32 | + | |
| 33 | +**Compromis :** | |
| 34 | + | |
| 35 | +- Ne peut pas utiliser les serveurs MCP locaux basés sur stdio | |
| 36 | +- Ne peut pas utiliser les serveurs MCP basés sur Docker qui n'exposent que stdio | |
| 37 | + | |
| 38 | +**Futur :** Le transport stdio sera ajouté lorsqu'il y aura une demande pour les serveurs MCP locaux. | |
| 39 | + | |
| 40 | +--- | |
| 41 | + | |
| 42 | +## Prise en charge du texte uniquement | |
| 43 | + | |
| 44 | +**Décision :** Ne prendre en charge que les outils MCP qui utilisent des entrées/sorties texte. Filtrer les outils binaires. | |
| 45 | + | |
| 46 | +**Justification :** | |
| 47 | + | |
| 48 | +1. **Philosophie de conception :** Mini-me est conçu comme un agent basé sur le texte | |
| 49 | +2. **Sécurité :** Les données binaires peuvent être dangereuses (images, exécutables, etc.) | |
| 50 | +3. **Simplicité :** Le texte est plus facile à gérer, journaliser et afficher | |
| 51 | +4. **Compatibilité :** Tous les principaux serveurs MCP (GitHub, Context7, Wikipedia) utilisent des outils texte uniquement | |
| 52 | +5. **Contexte :** La fenêtre de contexte de mini-me est basée sur le texte | |
| 53 | + | |
| 54 | +**Implémentation :** | |
| 55 | + | |
| 56 | +Les outils qui nécessitent des données binaires (format `binary` ou type `binary`) sont automatiquement filtrés lors de la découverte des outils, avec un avertissement affiché au démarrage. | |
| 57 | + | |
| 58 | +--- | |
| 59 | + | |
| 60 | +## Pas de transport stdio (pour l'instant) | |
| 61 | + | |
| 62 | +**Décision :** Reporter l'implémentation du transport stdio. | |
| 63 | + | |
| 64 | +**Justification :** | |
| 65 | + | |
| 66 | +1. **HTTP est suffisant :** Tous les serveurs MCP publics connus actuellement utilisent HTTP | |
| 67 | +2. **Complexité :** Stdio nécessite la gestion de sous-processus, le streaming d'entrée/sortie | |
| 68 | +3. **Priorités :** Se concentrer sur le transport HTTP en premier, ajouter stdio plus tard si nécessaire | |
| 69 | +4. **Cas d'utilisation :** Stdio est principalement pour les serveurs MCP locaux/auto-hébergés | |
| 70 | + | |
| 71 | +**Solution de contournement :** Utilisez des serveurs MCP basés sur HTTP ou exécutez des serveurs MCP comme services HTTP. | |
| 72 | + | |
| 73 | +**Futur :** Sera implémenté lorsque les utilisateurs demanderont des serveurs MCP locaux basés sur stdio. | |
| 74 | + | |
| 75 | +--- | |
| 76 | + | |
| 77 | +## Pas de prise en charge OAuth 2.0 | |
| 78 | + | |
| 79 | +**Décision :** Ne prendre en charge que l'authentification par token Bearer et par clé API. Pas de flux OAuth 2.0. | |
| 80 | + | |
| 81 | +**Justification :** | |
| 82 | + | |
| 83 | +1. **Simplicité :** OAuth 2.0 nécessite une gestion complexe du flux d'authentification via navigateur | |
| 84 | +2. **Cas d'utilisation :** Les serveurs MCP actuels n'ont pas besoin d'OAuth 2.0 | |
| 85 | +3. **Sécurité :** Les tokens Bearer et clés API sont plus simples à gérer avec les variables d'environnement | |
| 86 | +4. **Complexité :** OAuth 2.0 nécessite des callbacks, le stockage de l'état, la gestion des rafraîchissements de tokens | |
| 87 | + | |
| 88 | +**Compromis :** | |
| 89 | + | |
| 90 | +- Ne peut pas utiliser les serveurs MCP qui nécessitent OAuth 2.0 | |
| 91 | +- Les utilisateurs doivent utiliser des tokens personnels ou des clés API | |
| 92 | + | |
| 93 | +**Solution de contournement :** Utilisez des serveurs MCP auto-hébergés qui acceptent des tokens Bearer ou des clés API. | |
| 94 | + | |
| 95 | +**Futur :** L'implémentation d'OAuth 2.0 sera ajoutée si des serveurs MCP populaires l'exigent. | |
| 96 | + | |
| 97 | +--- | |
| 98 | + | |
| 99 | +## Pas de mise en cache | |
| 100 | + | |
| 101 | +**Décision :** Ne pas mettre en cache les résultats des outils MCP. | |
| 102 | + | |
| 103 | +**Justification :** | |
| 104 | + | |
| 105 | +1. **Actualité :** Les données des outils MCP (code, documentation, etc.) peuvent changer fréquemment | |
| 106 | +2. **Simplicité :** Pas besoin de gérer l'invalidation du cache | |
| 107 | +3. **Espace :** La mise en cache nécessiterait du stockage et de la gestion de la mémoire | |
| 108 | +4. **Prédictibilité :** Les utilisateurs s'attendent à des données en temps réel | |
| 109 | + | |
| 110 | +**Compromis :** | |
| 111 | + | |
| 112 | +- Réexécution des mêmes requêtes retourne des données fraîches | |
| 113 | +- Pas de gain de performance pour les requêtes répétées | |
| 114 | + | |
| 115 | +**Futur :** La mise en cache pourrait être ajoutée pour des outils spécifiques où c'est utile (ex : documentation statique). | |
| 116 | + | |
| 117 | +--- | |
| 118 | + | |
| 119 | +## Pas de découverte paresseuse | |
| 120 | + | |
| 121 | +**Décision :** Découvrir tous les outils MCP au démarrage, pas à la demande. | |
| 122 | + | |
| 123 | +**Justification :** | |
| 124 | + | |
| 125 | +1. **Prédictabilité :** Tous les outils sont disponibles dès le départ | |
| 126 | +2. **Simplicité :** Pas besoin de gérer la découverte à la demande | |
| 127 | +3. **Bannière :** La bannière de démarrage affiche tous les outils disponibles | |
| 128 | +4. **Erreurs préventives :** Les erreurs de configuration sont détectées tôt | |
| 129 | + | |
| 130 | +**Compromis :** | |
| 131 | + | |
| 132 | +- Temps de démarrage plus long si de nombreux serveurs MCP sont configurés | |
| 133 | +- Les serveurs MCP non disponibles ne sont pas disponibles pour toute la session | |
| 134 | + | |
| 135 | +**Futur :** La découverte paresseuse pourrait être implémentée pour améliorer les performances de démarrage. | |
| 136 | + | |
| 137 | +--- | |
| 138 | + | |
| 139 | +## Préfixage des noms d'outils | |
| 140 | + | |
| 141 | +**Décision :** Préfixer tous les noms d'outils MCP avec le nom du serveur. | |
| 142 | + | |
| 143 | +**Justification :** | |
| 144 | + | |
| 145 | +1. **Éviter les collisions :** Plusieurs serveurs peuvent avoir des outils avec le même nom (ex : `search`) | |
| 146 | +2. **Clarté :** Les noms préfixés indiquent clairement la source de l'outil (ex : `github_search_code`) | |
| 147 | +3. **Consistance :** Nettoyage des noms pour garantir des identifiants valides | |
| 148 | + | |
| 149 | +**Format :** `{nom_serveur}_{nom_original_outil}` | |
| 150 | + | |
| 151 | +**Nettoyage :** | |
| 152 | +- Les caractères non alphanumériques deviennent des underscores | |
| 153 | +- Les tirets deviennent des underscores | |
| 154 | +- Les underscores multiples sont réduits à un seul | |
| 155 | + | |
| 156 | +**Exemple :** | |
| 157 | +- `resolve-library-id` → `context7_resolve_library_id` | |
| 158 | +- `query-docs` → `context7_query_docs` | |
| 159 | + | |
| 160 | +--- | |
| 161 | + | |
| 162 | +## Filtrage des outils | |
| 163 | + | |
| 164 | +**Décision :** Permettre aux utilisateurs de filtrer quels outils de chaque serveur sont exposés. | |
| 165 | + | |
| 166 | +**Justification :** | |
| 167 | + | |
| 168 | +1. **Sécurité :** Limiter les outils disponibles réduit la surface d'attaque | |
| 169 | +2. **Simplicité :** Les utilisateurs n'ont pas besoin de voir tous les outils d'un serveur | |
| 170 | +3. **Performance :** Moins d'outils = démarrage plus rapide | |
| 171 | +4. **Contrôle :** Les utilisateurs peuvent désactiver des outils spécifiques qu'ils ne veulent pas | |
| 172 | + | |
| 173 | +**Implémentation :** | |
| 174 | + | |
| 175 | +```yaml | |
| 176 | +servers: | |
| 177 | + - name: github | |
| 178 | + tools: # Seuls ces outils seront disponibles | |
| 179 | + - get_me | |
| 180 | + - list_repositories | |
| 181 | + - search_code | |
| 182 | +``` | |
| 183 | + | |
| 184 | +--- | |
| 185 | + | |
| 186 | +## Authentification via variables d'environnement | |
| 187 | + | |
| 188 | +**Décision :** Charger les tokens d'authentification uniquement depuis des variables d'environnement. | |
| 189 | + | |
| 190 | +**Justification :** | |
| 191 | + | |
| 192 | +1. **Sécurité :** Évite de committer des secrets dans le contrôle de version | |
| 193 | +2. **Flexibilité :** Permet aux utilisateurs d'utiliser leurs propres mécanismes de gestion de secrets | |
| 194 | +3. **Compatibilité :** Fonctionne avec tous les systèmes (Linux, macOS, Windows) | |
| 195 | +4. **Bonnes pratiques :** Suit les principes de sécurité standard (12-factor app) | |
| 196 | + | |
| 197 | +**Implémentation :** | |
| 198 | + | |
| 199 | +```yaml | |
| 200 | +auth: | |
| 201 | + type: bearer | |
| 202 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 203 | +``` | |
| 204 | + | |
| 205 | +Le token est chargé depuis la variable d'environnement `GITHUB_PERSONAL_ACCESS_TOKEN`. | |
| 206 | + | |
| 207 | +--- | |
| 208 | + | |
| 209 | +## Système de permissions | |
| 210 | + | |
| 211 | +**Décision :** Implémenter un système de permissions pour les outils MCP. | |
| 212 | + | |
| 213 | +**Justification :** | |
| 214 | + | |
| 215 | +1. **Sécurité :** Prévenir l'exécution inattendue d'outils qui pourraient être dangereux | |
| 216 | +2. **Contrôle :** Donner aux utilisateurs le contrôle sur quels outils peuvent être exécutés | |
| 217 | +3. **Transparence :** Les utilisateurs savent quand un outil MCP est sur le point de s'exécuter | |
| 218 | + | |
| 219 | +**Modes :** | |
| 220 | + | |
| 221 | +- `ask` : Demande à l'utilisateur avant d'exécuter chaque outil MCP (par défaut) | |
| 222 | +- `auto` : Exécute les outils MCP automatiquement sans demande | |
| 223 | + | |
| 224 | +**Configuration globale :** | |
| 225 | + | |
| 226 | +```yaml | |
| 227 | +mcp: | |
| 228 | + permissions: ask # Mode par défaut pour tous les serveurs | |
| 229 | +``` | |
| 230 | + | |
| 231 | +**Configuration par serveur :** | |
| 232 | + | |
| 233 | +```yaml | |
| 234 | +servers: | |
| 235 | + - name: github | |
| 236 | + permissions: auto # Remplace le mode global pour ce serveur | |
| 237 | +``` | |
| 238 | + | |
| 239 | +--- | |
| 240 | + | |
| 241 | +## Gestion des erreurs | |
| 242 | + | |
| 243 | +**Décision :** Gérer les erreurs MCP de manière transparente avec des messages clairs. | |
| 244 | + | |
| 245 | +**Justification :** | |
| 246 | + | |
| 247 | +1. **Expérience utilisateur :** Les utilisateurs doivent comprendre pourquoi un outil a échoué | |
| 248 | +2. **Débogage :** Les messages d'erreur clairs aident à diagnostiquer les problèmes | |
| 249 | +3. **Résilience :** Mini-me doit continuer à fonctionner même si un serveur MCP échoue | |
| 250 | + | |
| 251 | +**Stratégie :** | |
| 252 | + | |
| 253 | +- **Erreurs de configuration** : Détectées au démarrage, affichées comme avertissements | |
| 254 | +- **Erreurs de connexion** : Le serveur est ignoré avec un avertissement | |
| 255 | +- **Erreurs d'exécution** : Retournées à l'utilisateur avec un message clair | |
| 256 | +- **Erreurs de timeout** : Gérées comme des échecs de connexion | |
| 257 | + | |
| 258 | +--- | |
| 259 | + | |
| 260 | +## Parsing SSE | |
| 261 | + | |
| 262 | +**Décision :** Implémenter un parseur SSE (Server-Sent Events) robuste pour les réponses MCP. | |
| 263 | + | |
| 264 | +**Justification :** | |
| 265 | + | |
| 266 | +1. **Spécification MCP :** Le protocole MCP utilise SSE pour la communication | |
| 267 | +2. **Fiabilité :** Le parsing doit gérer les événements partiels, les erreurs, les timeouts | |
| 268 | +3. **Performance :** Le parsing doit être efficace pour ne pas ralentir les requêtes | |
| 269 | + | |
| 270 | +**Implémentation :** | |
| 271 | + | |
| 272 | +Le parseur SSE de mini-me gère : | |
| 273 | +- Les événements complets | |
| 274 | +- Les événements partiaux (streaming) | |
| 275 | +- Les erreurs de parsing | |
| 276 | +- Les timeouts | |
| 277 | + | |
| 278 | +--- | |
| 279 | + | |
| 280 | +## Voir aussi | |
| 281 | + | |
| 282 | +- [Architecture MCP](architecture.md) | |
| 283 | +- [Configuration MCP](reference/configuration.md) | |
| 284 | +- [Outils MCP disponibles](reference/outils-disponibles.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,284 @@ | |||
| 1 | +# Décisions de conception MCP | ||
| 2 | + | ||
| 3 | +Ce document explique les principales décisions de conception prises lors de l'implémentation de la prise en charge MCP (Model Context Protocol) dans mini-me. | ||
| 4 | + | ||
| 5 | +## Table des matières | ||
| 6 | + | ||
| 7 | +- [Transport HTTP uniquement](#transport-http-uniquement) | ||
| 8 | +- [Prise en charge du texte uniquement](#prise-en-charge-du-texte-uniquement) | ||
| 9 | +- [Pas de transport stdio (pour l'instant)](#pas-de-transport-stdio-pour-linstant) | ||
| 10 | +- [Pas de prise en charge OAuth 2.0](#pas-de-prise-en-charge-oauth-20) | ||
| 11 | +- [Pas de mise en cache](#pas-de-mise-en-cache) | ||
| 12 | +- [Pas de découverte paresseuse](#pas-de-découverte-paresseuse) | ||
| 13 | +- [Préfixage des noms d'outils](#préfixage-des-noms-doutils) | ||
| 14 | +- [Filtrage des outils](#filtrage-des-outils) | ||
| 15 | +- [Authentification via variables d'environnement](#authentification-via-variables-denvironnement) | ||
| 16 | +- [Système de permissions](#système-de-permissions) | ||
| 17 | +- [Gestion des erreurs](#gestion-des-erreurs) | ||
| 18 | + | ||
| 19 | +--- | ||
| 20 | + | ||
| 21 | +## Transport HTTP uniquement | ||
| 22 | + | ||
| 23 | +**Décision :** Implémenter uniquement le transport HTTP (HTTP streamable), pas stdio. | ||
| 24 | + | ||
| 25 | +**Justification :** | ||
| 26 | + | ||
| 27 | +1. **Simplicité :** Le transport HTTP est plus simple à implémenter et à déboguer | ||
| 28 | +2. **Cas d'utilisation courant :** La plupart des serveurs MCP hébergés utilisent le transport HTTP | ||
| 29 | +3. **Sécurité :** HTTP sur TLS fournit une sécurité intégrée | ||
| 30 | +4. **Compatibilité :** Fonctionne avec tous les services MCP hébergés (GitHub, Context7, Wikipedia, etc.) | ||
| 31 | +5. **Effort d'implémentation :** Stdio nécessite la gestion de sous-processus, ce qui ajoute de la complexité | ||
| 32 | + | ||
| 33 | +**Compromis :** | ||
| 34 | + | ||
| 35 | +- Ne peut pas utiliser les serveurs MCP locaux basés sur stdio | ||
| 36 | +- Ne peut pas utiliser les serveurs MCP basés sur Docker qui n'exposent que stdio | ||
| 37 | + | ||
| 38 | +**Futur :** Le transport stdio sera ajouté lorsqu'il y aura une demande pour les serveurs MCP locaux. | ||
| 39 | + | ||
| 40 | +--- | ||
| 41 | + | ||
| 42 | +## Prise en charge du texte uniquement | ||
| 43 | + | ||
| 44 | +**Décision :** Ne prendre en charge que les outils MCP qui utilisent des entrées/sorties texte. Filtrer les outils binaires. | ||
| 45 | + | ||
| 46 | +**Justification :** | ||
| 47 | + | ||
| 48 | +1. **Philosophie de conception :** Mini-me est conçu comme un agent basé sur le texte | ||
| 49 | +2. **Sécurité :** Les données binaires peuvent être dangereuses (images, exécutables, etc.) | ||
| 50 | +3. **Simplicité :** Le texte est plus facile à gérer, journaliser et afficher | ||
| 51 | +4. **Compatibilité :** Tous les principaux serveurs MCP (GitHub, Context7, Wikipedia) utilisent des outils texte uniquement | ||
| 52 | +5. **Contexte :** La fenêtre de contexte de mini-me est basée sur le texte | ||
| 53 | + | ||
| 54 | +**Implémentation :** | ||
| 55 | + | ||
| 56 | +Les outils qui nécessitent des données binaires (format `binary` ou type `binary`) sont automatiquement filtrés lors de la découverte des outils, avec un avertissement affiché au démarrage. | ||
| 57 | + | ||
| 58 | +--- | ||
| 59 | + | ||
| 60 | +## Pas de transport stdio (pour l'instant) | ||
| 61 | + | ||
| 62 | +**Décision :** Reporter l'implémentation du transport stdio. | ||
| 63 | + | ||
| 64 | +**Justification :** | ||
| 65 | + | ||
| 66 | +1. **HTTP est suffisant :** Tous les serveurs MCP publics connus actuellement utilisent HTTP | ||
| 67 | +2. **Complexité :** Stdio nécessite la gestion de sous-processus, le streaming d'entrée/sortie | ||
| 68 | +3. **Priorités :** Se concentrer sur le transport HTTP en premier, ajouter stdio plus tard si nécessaire | ||
| 69 | +4. **Cas d'utilisation :** Stdio est principalement pour les serveurs MCP locaux/auto-hébergés | ||
| 70 | + | ||
| 71 | +**Solution de contournement :** Utilisez des serveurs MCP basés sur HTTP ou exécutez des serveurs MCP comme services HTTP. | ||
| 72 | + | ||
| 73 | +**Futur :** Sera implémenté lorsque les utilisateurs demanderont des serveurs MCP locaux basés sur stdio. | ||
| 74 | + | ||
| 75 | +--- | ||
| 76 | + | ||
| 77 | +## Pas de prise en charge OAuth 2.0 | ||
| 78 | + | ||
| 79 | +**Décision :** Ne prendre en charge que l'authentification par token Bearer et par clé API. Pas de flux OAuth 2.0. | ||
| 80 | + | ||
| 81 | +**Justification :** | ||
| 82 | + | ||
| 83 | +1. **Simplicité :** OAuth 2.0 nécessite une gestion complexe du flux d'authentification via navigateur | ||
| 84 | +2. **Cas d'utilisation :** Les serveurs MCP actuels n'ont pas besoin d'OAuth 2.0 | ||
| 85 | +3. **Sécurité :** Les tokens Bearer et clés API sont plus simples à gérer avec les variables d'environnement | ||
| 86 | +4. **Complexité :** OAuth 2.0 nécessite des callbacks, le stockage de l'état, la gestion des rafraîchissements de tokens | ||
| 87 | + | ||
| 88 | +**Compromis :** | ||
| 89 | + | ||
| 90 | +- Ne peut pas utiliser les serveurs MCP qui nécessitent OAuth 2.0 | ||
| 91 | +- Les utilisateurs doivent utiliser des tokens personnels ou des clés API | ||
| 92 | + | ||
| 93 | +**Solution de contournement :** Utilisez des serveurs MCP auto-hébergés qui acceptent des tokens Bearer ou des clés API. | ||
| 94 | + | ||
| 95 | +**Futur :** L'implémentation d'OAuth 2.0 sera ajoutée si des serveurs MCP populaires l'exigent. | ||
| 96 | + | ||
| 97 | +--- | ||
| 98 | + | ||
| 99 | +## Pas de mise en cache | ||
| 100 | + | ||
| 101 | +**Décision :** Ne pas mettre en cache les résultats des outils MCP. | ||
| 102 | + | ||
| 103 | +**Justification :** | ||
| 104 | + | ||
| 105 | +1. **Actualité :** Les données des outils MCP (code, documentation, etc.) peuvent changer fréquemment | ||
| 106 | +2. **Simplicité :** Pas besoin de gérer l'invalidation du cache | ||
| 107 | +3. **Espace :** La mise en cache nécessiterait du stockage et de la gestion de la mémoire | ||
| 108 | +4. **Prédictibilité :** Les utilisateurs s'attendent à des données en temps réel | ||
| 109 | + | ||
| 110 | +**Compromis :** | ||
| 111 | + | ||
| 112 | +- Réexécution des mêmes requêtes retourne des données fraîches | ||
| 113 | +- Pas de gain de performance pour les requêtes répétées | ||
| 114 | + | ||
| 115 | +**Futur :** La mise en cache pourrait être ajoutée pour des outils spécifiques où c'est utile (ex : documentation statique). | ||
| 116 | + | ||
| 117 | +--- | ||
| 118 | + | ||
| 119 | +## Pas de découverte paresseuse | ||
| 120 | + | ||
| 121 | +**Décision :** Découvrir tous les outils MCP au démarrage, pas à la demande. | ||
| 122 | + | ||
| 123 | +**Justification :** | ||
| 124 | + | ||
| 125 | +1. **Prédictabilité :** Tous les outils sont disponibles dès le départ | ||
| 126 | +2. **Simplicité :** Pas besoin de gérer la découverte à la demande | ||
| 127 | +3. **Bannière :** La bannière de démarrage affiche tous les outils disponibles | ||
| 128 | +4. **Erreurs préventives :** Les erreurs de configuration sont détectées tôt | ||
| 129 | + | ||
| 130 | +**Compromis :** | ||
| 131 | + | ||
| 132 | +- Temps de démarrage plus long si de nombreux serveurs MCP sont configurés | ||
| 133 | +- Les serveurs MCP non disponibles ne sont pas disponibles pour toute la session | ||
| 134 | + | ||
| 135 | +**Futur :** La découverte paresseuse pourrait être implémentée pour améliorer les performances de démarrage. | ||
| 136 | + | ||
| 137 | +--- | ||
| 138 | + | ||
| 139 | +## Préfixage des noms d'outils | ||
| 140 | + | ||
| 141 | +**Décision :** Préfixer tous les noms d'outils MCP avec le nom du serveur. | ||
| 142 | + | ||
| 143 | +**Justification :** | ||
| 144 | + | ||
| 145 | +1. **Éviter les collisions :** Plusieurs serveurs peuvent avoir des outils avec le même nom (ex : `search`) | ||
| 146 | +2. **Clarté :** Les noms préfixés indiquent clairement la source de l'outil (ex : `github_search_code`) | ||
| 147 | +3. **Consistance :** Nettoyage des noms pour garantir des identifiants valides | ||
| 148 | + | ||
| 149 | +**Format :** `{nom_serveur}_{nom_original_outil}` | ||
| 150 | + | ||
| 151 | +**Nettoyage :** | ||
| 152 | +- Les caractères non alphanumériques deviennent des underscores | ||
| 153 | +- Les tirets deviennent des underscores | ||
| 154 | +- Les underscores multiples sont réduits à un seul | ||
| 155 | + | ||
| 156 | +**Exemple :** | ||
| 157 | +- `resolve-library-id` → `context7_resolve_library_id` | ||
| 158 | +- `query-docs` → `context7_query_docs` | ||
| 159 | + | ||
| 160 | +--- | ||
| 161 | + | ||
| 162 | +## Filtrage des outils | ||
| 163 | + | ||
| 164 | +**Décision :** Permettre aux utilisateurs de filtrer quels outils de chaque serveur sont exposés. | ||
| 165 | + | ||
| 166 | +**Justification :** | ||
| 167 | + | ||
| 168 | +1. **Sécurité :** Limiter les outils disponibles réduit la surface d'attaque | ||
| 169 | +2. **Simplicité :** Les utilisateurs n'ont pas besoin de voir tous les outils d'un serveur | ||
| 170 | +3. **Performance :** Moins d'outils = démarrage plus rapide | ||
| 171 | +4. **Contrôle :** Les utilisateurs peuvent désactiver des outils spécifiques qu'ils ne veulent pas | ||
| 172 | + | ||
| 173 | +**Implémentation :** | ||
| 174 | + | ||
| 175 | +```yaml | ||
| 176 | +servers: | ||
| 177 | + - name: github | ||
| 178 | + tools: # Seuls ces outils seront disponibles | ||
| 179 | + - get_me | ||
| 180 | + - list_repositories | ||
| 181 | + - search_code | ||
| 182 | +``` | ||
| 183 | + | ||
| 184 | +--- | ||
| 185 | + | ||
| 186 | +## Authentification via variables d'environnement | ||
| 187 | + | ||
| 188 | +**Décision :** Charger les tokens d'authentification uniquement depuis des variables d'environnement. | ||
| 189 | + | ||
| 190 | +**Justification :** | ||
| 191 | + | ||
| 192 | +1. **Sécurité :** Évite de committer des secrets dans le contrôle de version | ||
| 193 | +2. **Flexibilité :** Permet aux utilisateurs d'utiliser leurs propres mécanismes de gestion de secrets | ||
| 194 | +3. **Compatibilité :** Fonctionne avec tous les systèmes (Linux, macOS, Windows) | ||
| 195 | +4. **Bonnes pratiques :** Suit les principes de sécurité standard (12-factor app) | ||
| 196 | + | ||
| 197 | +**Implémentation :** | ||
| 198 | + | ||
| 199 | +```yaml | ||
| 200 | +auth: | ||
| 201 | + type: bearer | ||
| 202 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 203 | +``` | ||
| 204 | + | ||
| 205 | +Le token est chargé depuis la variable d'environnement `GITHUB_PERSONAL_ACCESS_TOKEN`. | ||
| 206 | + | ||
| 207 | +--- | ||
| 208 | + | ||
| 209 | +## Système de permissions | ||
| 210 | + | ||
| 211 | +**Décision :** Implémenter un système de permissions pour les outils MCP. | ||
| 212 | + | ||
| 213 | +**Justification :** | ||
| 214 | + | ||
| 215 | +1. **Sécurité :** Prévenir l'exécution inattendue d'outils qui pourraient être dangereux | ||
| 216 | +2. **Contrôle :** Donner aux utilisateurs le contrôle sur quels outils peuvent être exécutés | ||
| 217 | +3. **Transparence :** Les utilisateurs savent quand un outil MCP est sur le point de s'exécuter | ||
| 218 | + | ||
| 219 | +**Modes :** | ||
| 220 | + | ||
| 221 | +- `ask` : Demande à l'utilisateur avant d'exécuter chaque outil MCP (par défaut) | ||
| 222 | +- `auto` : Exécute les outils MCP automatiquement sans demande | ||
| 223 | + | ||
| 224 | +**Configuration globale :** | ||
| 225 | + | ||
| 226 | +```yaml | ||
| 227 | +mcp: | ||
| 228 | + permissions: ask # Mode par défaut pour tous les serveurs | ||
| 229 | +``` | ||
| 230 | + | ||
| 231 | +**Configuration par serveur :** | ||
| 232 | + | ||
| 233 | +```yaml | ||
| 234 | +servers: | ||
| 235 | + - name: github | ||
| 236 | + permissions: auto # Remplace le mode global pour ce serveur | ||
| 237 | +``` | ||
| 238 | + | ||
| 239 | +--- | ||
| 240 | + | ||
| 241 | +## Gestion des erreurs | ||
| 242 | + | ||
| 243 | +**Décision :** Gérer les erreurs MCP de manière transparente avec des messages clairs. | ||
| 244 | + | ||
| 245 | +**Justification :** | ||
| 246 | + | ||
| 247 | +1. **Expérience utilisateur :** Les utilisateurs doivent comprendre pourquoi un outil a échoué | ||
| 248 | +2. **Débogage :** Les messages d'erreur clairs aident à diagnostiquer les problèmes | ||
| 249 | +3. **Résilience :** Mini-me doit continuer à fonctionner même si un serveur MCP échoue | ||
| 250 | + | ||
| 251 | +**Stratégie :** | ||
| 252 | + | ||
| 253 | +- **Erreurs de configuration** : Détectées au démarrage, affichées comme avertissements | ||
| 254 | +- **Erreurs de connexion** : Le serveur est ignoré avec un avertissement | ||
| 255 | +- **Erreurs d'exécution** : Retournées à l'utilisateur avec un message clair | ||
| 256 | +- **Erreurs de timeout** : Gérées comme des échecs de connexion | ||
| 257 | + | ||
| 258 | +--- | ||
| 259 | + | ||
| 260 | +## Parsing SSE | ||
| 261 | + | ||
| 262 | +**Décision :** Implémenter un parseur SSE (Server-Sent Events) robuste pour les réponses MCP. | ||
| 263 | + | ||
| 264 | +**Justification :** | ||
| 265 | + | ||
| 266 | +1. **Spécification MCP :** Le protocole MCP utilise SSE pour la communication | ||
| 267 | +2. **Fiabilité :** Le parsing doit gérer les événements partiels, les erreurs, les timeouts | ||
| 268 | +3. **Performance :** Le parsing doit être efficace pour ne pas ralentir les requêtes | ||
| 269 | + | ||
| 270 | +**Implémentation :** | ||
| 271 | + | ||
| 272 | +Le parseur SSE de mini-me gère : | ||
| 273 | +- Les événements complets | ||
| 274 | +- Les événements partiaux (streaming) | ||
| 275 | +- Les erreurs de parsing | ||
| 276 | +- Les timeouts | ||
| 277 | + | ||
| 278 | +--- | ||
| 279 | + | ||
| 280 | +## Voir aussi | ||
| 281 | + | ||
| 282 | +- [Architecture MCP](architecture.md) | ||
| 283 | +- [Configuration MCP](reference/configuration.md) | ||
| 284 | +- [Outils MCP disponibles](reference/outils-disponibles.md) | ||
added
docs/fr/how-to/mcp-utiliser-context7.md +70 -0 | new file mode 100644 | ||
| @@ -0,0 +1,70 @@ | ||
| 1 | +# Comment utiliser Context7 pour la documentation | |
| 2 | + | |
| 3 | +Ce guide montre comment utiliser le serveur Context7 MCP pour rechercher et consulter de la documentation technique. | |
| 4 | + | |
| 5 | +## Prérequis | |
| 6 | +- MCP activé dans mini-me | |
| 7 | +- Une clé API Context7 valide: `CONTEXT7_API_KEY` | |
| 8 | +- Le serveur Context7 configuré dans votre fichier de configuration | |
| 9 | + | |
| 10 | +## Étapes | |
| 11 | + | |
| 12 | +### 1. Obtenir un ID de bibliothèque | |
| 13 | + | |
| 14 | +Avant de pouvoir interroger la documentation, vous devez obtenir un **ID de bibliothèque Context7**. | |
| 15 | + | |
| 16 | +Utilisez l'outil `context7_resolve_library_id` avec deux paramètres obligatoires : | |
| 17 | +- `query` : Ce que vous cherchez (ex: "React hooks") | |
| 18 | +- `libraryName` : Le nom de la bibliothèque (ex: "react") | |
| 19 | + | |
| 20 | +**Exemple de requête :** | |
| 21 | +``` | |
| 22 | +"Je veux comprendre comment fonctionne useEffect dans React" | |
| 23 | +``` | |
| 24 | + | |
| 25 | +Le modèle devrait appeler : | |
| 26 | +``` | |
| 27 | +context7_resolve_library_id(query="useEffect React", libraryName="react") | |
| 28 | +``` | |
| 29 | + | |
| 30 | +**Résultat attendu :** Un ID de bibliothèque au format `/org/project` (ex: `/facebook/react`) | |
| 31 | + | |
| 32 | +### 2. Interroger la documentation | |
| 33 | + | |
| 34 | +Une fois que vous avez l'ID de bibliothèque, utilisez `context7_query_docs` avec : | |
| 35 | +- `libraryId` : L'ID obtenu à l'étape 1 (ex: `/facebook/react`) | |
| 36 | +- `query` : Votre question spécifique (ex: "useEffect cleanup function") | |
| 37 | + | |
| 38 | +**Exemple de requête :** | |
| 39 | +``` | |
| 40 | +"Comment nettoyer un useEffect dans React ?" | |
| 41 | +``` | |
| 42 | + | |
| 43 | +Le modèle devrait appeler : | |
| 44 | +``` | |
| 45 | +context7_query_docs(libraryId="/facebook/react", query="useEffect cleanup") | |
| 46 | +``` | |
| 47 | + | |
| 48 | +## Cas d'usage courants | |
| 49 | + | |
| 50 | +| Besoin | Requête exemple | Outils utilisés | | |
| 51 | +|--------|-----------------|-----------------| | |
| 52 | +| Comprendre une bibliothèque | "Explique-moi comment fonctionne Redux" | resolve-library-id, query-docs | | |
| 53 | +| Trouver des exemples | "Montre-moi des exemples d'utilisation de useState" | resolve-library-id, query-docs | | |
| 54 | +| Résoudre un problème | "Pourquoi mon useEffect s'exécute plusieurs fois ?" | resolve-library-id, query-docs | | |
| 55 | +| Explorer une API | "Quelles sont les méthodes disponibles dans axios ?" | resolve-library-id, query-docs | | |
| 56 | + | |
| 57 | +## Variantes | |
| 58 | + | |
| 59 | +- **Si vous connaissez déjà l'ID de bibliothèque** : Vous pouvez appeler directement `context7_query_docs` sans passer par `resolve-library-id` | |
| 60 | +- **Pour plusieurs bibliothèques** : Appelez `resolve-library-id` pour chaque bibliothèque, puis `query-docs` pour chacune | |
| 61 | + | |
| 62 | +## Limites | |
| 63 | + | |
| 64 | +- Maximum **3 appels** à `resolve-library-id` par question | |
| 65 | +- Maximum **3 appels** à `query-docs` par question | |
| 66 | +- Les résultats dépendent de la couverture de la bibliothèque dans la base Context7 | |
| 67 | + | |
| 68 | +## Voir aussi | |
| 69 | +- Référence des outils : [Outils disponibles](../reference/outils-disponibles.md) | |
| 70 | +- Pourquoi Context7 : [Décisions de conception](../explanation/decisions.md#context7) | |
| new file mode 100644 | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | +# Comment utiliser Context7 pour la documentation | ||
| 2 | + | ||
| 3 | +Ce guide montre comment utiliser le serveur Context7 MCP pour rechercher et consulter de la documentation technique. | ||
| 4 | + | ||
| 5 | +## Prérequis | ||
| 6 | +- MCP activé dans mini-me | ||
| 7 | +- Une clé API Context7 valide: `CONTEXT7_API_KEY` | ||
| 8 | +- Le serveur Context7 configuré dans votre fichier de configuration | ||
| 9 | + | ||
| 10 | +## Étapes | ||
| 11 | + | ||
| 12 | +### 1. Obtenir un ID de bibliothèque | ||
| 13 | + | ||
| 14 | +Avant de pouvoir interroger la documentation, vous devez obtenir un **ID de bibliothèque Context7**. | ||
| 15 | + | ||
| 16 | +Utilisez l'outil `context7_resolve_library_id` avec deux paramètres obligatoires : | ||
| 17 | +- `query` : Ce que vous cherchez (ex: "React hooks") | ||
| 18 | +- `libraryName` : Le nom de la bibliothèque (ex: "react") | ||
| 19 | + | ||
| 20 | +**Exemple de requête :** | ||
| 21 | +``` | ||
| 22 | +"Je veux comprendre comment fonctionne useEffect dans React" | ||
| 23 | +``` | ||
| 24 | + | ||
| 25 | +Le modèle devrait appeler : | ||
| 26 | +``` | ||
| 27 | +context7_resolve_library_id(query="useEffect React", libraryName="react") | ||
| 28 | +``` | ||
| 29 | + | ||
| 30 | +**Résultat attendu :** Un ID de bibliothèque au format `/org/project` (ex: `/facebook/react`) | ||
| 31 | + | ||
| 32 | +### 2. Interroger la documentation | ||
| 33 | + | ||
| 34 | +Une fois que vous avez l'ID de bibliothèque, utilisez `context7_query_docs` avec : | ||
| 35 | +- `libraryId` : L'ID obtenu à l'étape 1 (ex: `/facebook/react`) | ||
| 36 | +- `query` : Votre question spécifique (ex: "useEffect cleanup function") | ||
| 37 | + | ||
| 38 | +**Exemple de requête :** | ||
| 39 | +``` | ||
| 40 | +"Comment nettoyer un useEffect dans React ?" | ||
| 41 | +``` | ||
| 42 | + | ||
| 43 | +Le modèle devrait appeler : | ||
| 44 | +``` | ||
| 45 | +context7_query_docs(libraryId="/facebook/react", query="useEffect cleanup") | ||
| 46 | +``` | ||
| 47 | + | ||
| 48 | +## Cas d'usage courants | ||
| 49 | + | ||
| 50 | +| Besoin | Requête exemple | Outils utilisés | | ||
| 51 | +|--------|-----------------|-----------------| | ||
| 52 | +| Comprendre une bibliothèque | "Explique-moi comment fonctionne Redux" | resolve-library-id, query-docs | | ||
| 53 | +| Trouver des exemples | "Montre-moi des exemples d'utilisation de useState" | resolve-library-id, query-docs | | ||
| 54 | +| Résoudre un problème | "Pourquoi mon useEffect s'exécute plusieurs fois ?" | resolve-library-id, query-docs | | ||
| 55 | +| Explorer une API | "Quelles sont les méthodes disponibles dans axios ?" | resolve-library-id, query-docs | | ||
| 56 | + | ||
| 57 | +## Variantes | ||
| 58 | + | ||
| 59 | +- **Si vous connaissez déjà l'ID de bibliothèque** : Vous pouvez appeler directement `context7_query_docs` sans passer par `resolve-library-id` | ||
| 60 | +- **Pour plusieurs bibliothèques** : Appelez `resolve-library-id` pour chaque bibliothèque, puis `query-docs` pour chacune | ||
| 61 | + | ||
| 62 | +## Limites | ||
| 63 | + | ||
| 64 | +- Maximum **3 appels** à `resolve-library-id` par question | ||
| 65 | +- Maximum **3 appels** à `query-docs` par question | ||
| 66 | +- Les résultats dépendent de la couverture de la bibliothèque dans la base Context7 | ||
| 67 | + | ||
| 68 | +## Voir aussi | ||
| 69 | +- Référence des outils : [Outils disponibles](../reference/outils-disponibles.md) | ||
| 70 | +- Pourquoi Context7 : [Décisions de conception](../explanation/decisions.md#context7) | ||
added
docs/fr/how-to/mcp-utiliser-github.md +198 -0 | new file mode 100644 | ||
| @@ -0,0 +1,198 @@ | ||
| 1 | +# Utiliser GitHub pour l'accès au code | |
| 2 | + | |
| 3 | +Ce guide montre comment utiliser le serveur MCP GitHub pour interagir avec les dépôts GitHub, les issues, les pull requests, et plus. | |
| 4 | + | |
| 5 | +## Prérequis | |
| 6 | + | |
| 7 | +- MCP activé dans votre configuration mini-me | |
| 8 | +- Serveur MCP GitHub configuré | |
| 9 | +- **Token d'accès personnel GitHub** avec les bonnes autorisations | |
| 10 | + | |
| 11 | +## Exemple de configuration | |
| 12 | + | |
| 13 | +Ajoutez ceci à votre fichier `agent.yaml` ou votre fichier de configuration personnalisé : | |
| 14 | + | |
| 15 | +```yaml | |
| 16 | +mcp: | |
| 17 | + enabled: true | |
| 18 | + servers: | |
| 19 | + - name: github | |
| 20 | + transport: http | |
| 21 | + url: https://api.githubcopilot.com/mcp/ | |
| 22 | + timeout: 60s | |
| 23 | + auth: | |
| 24 | + type: bearer | |
| 25 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 26 | + headers: | |
| 27 | + Accept: "application/json, text/event-stream" | |
| 28 | + # Optionnel : Filtrer les outils à exposer | |
| 29 | + tools: | |
| 30 | + - get_me | |
| 31 | + - list_repositories | |
| 32 | + - list_issues | |
| 33 | + - list_pull_requests | |
| 34 | + - get_file_contents | |
| 35 | + - search_code | |
| 36 | + - search_repositories | |
| 37 | + - create_pull_request | |
| 38 | + - add_issue_comment | |
| 39 | +``` | |
| 40 | + | |
| 41 | +## Configuration de l'authentification | |
| 42 | + | |
| 43 | +1. **Obtenir un PAT GitHub :** | |
| 44 | + - Allez sur [https://github.com/settings/tokens](https://github.com/settings/tokens) | |
| 45 | + - Cliquez sur "Generate new token" | |
| 46 | + - Sélectionnez le type de token "Classic" | |
| 47 | + | |
| 48 | +2. **Autorisations requises :** | |
| 49 | + - Pour l'accès aux dépôts : `repo` (contrôle total des dépôts privés) | |
| 50 | + - Pour lire le profil utilisateur : `user` (read:user) | |
| 51 | + - Pour lire les organisations : `read:org` | |
| 52 | + - Pour les issues et pull requests : `repo` ou `public_repo` | |
| 53 | + - Pour la recherche de code : `repo` | |
| 54 | + | |
| 55 | +3. **Définir la variable d'environnement :** | |
| 56 | + ```bash | |
| 57 | + export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_votre_token_ici | |
| 58 | + ``` | |
| 59 | + | |
| 60 | +## Outils disponibles | |
| 61 | + | |
| 62 | +Le serveur MCP GitHub fournit de nombreux outils organisés par catégorie : | |
| 63 | + | |
| 64 | +### Informations sur le dépôt | |
| 65 | +- `github_get_me` - Obtenir le profil utilisateur authentifié | |
| 66 | +- `github_list_repositories` - Lister les dépôts de l'utilisateur | |
| 67 | +- `github_get_repository` - Obtenir les détails d'un dépôt | |
| 68 | + | |
| 69 | +### Accès au code | |
| 70 | +- `github_get_file_contents` - Obtenir le contenu d'un fichier depuis un dépôt | |
| 71 | +- `github_list_branches` - Lister les branches d'un dépôt | |
| 72 | +- `github_list_commits` - Lister les commits d'un dépôt | |
| 73 | +- `github_get_commit` - Obtenir les détails d'un commit | |
| 74 | + | |
| 75 | +### Issues et Pull Requests | |
| 76 | +- `github_list_issues` - Lister les issues d'un dépôt | |
| 77 | +- `github_list_pull_requests` - Lister les pull requests d'un dépôt | |
| 78 | +- `github_create_pull_request` - Créer une nouvelle pull request | |
| 79 | +- `github_add_issue_comment` - Ajouter un commentaire à une issue | |
| 80 | +- `github_update_issue_comment` - Mettre à jour un commentaire existant | |
| 81 | + | |
| 82 | +### Recherche | |
| 83 | +- `github_search_code` - Rechercher du code dans les dépôts | |
| 84 | +- `github_search_issues` - Rechercher des issues avec une correspondance sémantique | |
| 85 | +- `github_search_pull_requests` - Rechercher des pull requests | |
| 86 | +- `github_search_repositories` - Rechercher des dépôts | |
| 87 | +- `github_search_commits` - Rechercher des commits | |
| 88 | +- `github_search_users` - Rechercher des utilisateurs | |
| 89 | + | |
| 90 | +### Gestion de dépôt | |
| 91 | +- `github_create_repository` - Créer un nouveau dépôt | |
| 92 | +- `github_create_branch` - Créer une nouvelle branche | |
| 93 | +- `github_fork_repository` - Forker un dépôt | |
| 94 | +- `github_merge_pull_request` - Fusionner une pull request | |
| 95 | +- `github_delete_file` - Supprimer un fichier d'un dépôt | |
| 96 | + | |
| 97 | +### Avancé | |
| 98 | +- `github_run_secret_scanning` - Analyser le contenu à la recherche de secrets | |
| 99 | +- `github_assign_copilot_to_issue` - Assigner GitHub Copilot à une issue | |
| 100 | +- `github_get_copilot_job_status` - Obtenir l'état d'un job Copilot | |
| 101 | +- `github_create_pull_request_with_copilot` - Créer une PR avec l'agent Copilot | |
| 102 | + | |
| 103 | +## Exemples d'utilisation | |
| 104 | + | |
| 105 | +### Obtenir votre profil | |
| 106 | + | |
| 107 | +``` | |
| 108 | +Utilisez l'outil github_get_me pour obtenir mes informations utilisateur GitHub | |
| 109 | +``` | |
| 110 | + | |
| 111 | +### Lister vos dépôts | |
| 112 | + | |
| 113 | +``` | |
| 114 | +Utilisez l'outil github_list_repositories pour lister tous mes dépôts | |
| 115 | +``` | |
| 116 | + | |
| 117 | +### Rechercher du code | |
| 118 | + | |
| 119 | +``` | |
| 120 | +Utilisez l'outil github_search_code pour trouver des références à "context" dans le dépôt golang/go | |
| 121 | +``` | |
| 122 | + | |
| 123 | +### Obtenir le contenu d'un fichier | |
| 124 | + | |
| 125 | +``` | |
| 126 | +Utilisez l'outil github_get_file_contents pour obtenir le fichier README.md depuis owner/repo sur la branche main | |
| 127 | +``` | |
| 128 | + | |
| 129 | +### Créer une Pull Request | |
| 130 | + | |
| 131 | +``` | |
| 132 | +Utilisez l'outil github_create_pull_request pour créer une PR dans owner/repo avec le titre "Fix bug", head "feature-branch", base "main" | |
| 133 | +``` | |
| 134 | + | |
| 135 | +### Ajouter un commentaire à une issue | |
| 136 | + | |
| 137 | +``` | |
| 138 | +Utilisez l'outil github_add_issue_comment pour ajouter un commentaire "Merci pour le signalement !" à l'issue #123 dans owner/repo | |
| 139 | +``` | |
| 140 | + | |
| 141 | +## Paramètres des outils | |
| 142 | + | |
| 143 | +La plupart des outils GitHub nécessitent ces paramètres communs : | |
| 144 | + | |
| 145 | +- `owner` - Propriétaire du dépôt (nom d'utilisateur ou organisation) | |
| 146 | +- `repo` - Nom du dépôt | |
| 147 | +- Souvent les deux sont marqués avec `x-mcp-header` pour indiquer qu'ils doivent être passés comme en-têtes | |
| 148 | + | |
| 149 | +Par exemple : | |
| 150 | +```json | |
| 151 | +{ | |
| 152 | + "owner": "octocat", | |
| 153 | + "repo": "Hello-World" | |
| 154 | +} | |
| 155 | +``` | |
| 156 | + | |
| 157 | +## Conseils | |
| 158 | + | |
| 159 | +1. **Soyez spécifique avec owner/repo :** Vérifiez toujours le propriétaire et le nom du dépôt avant de continuer | |
| 160 | +2. **Utilisez des filtres :** Lors de la recherche, utilisez les qualificateurs de recherche GitHub comme `language:go`, `org:`, `user:` | |
| 161 | +3. **Gérez les grandes réponses :** Certains outils retournent de grandes quantités de données ; envisagez de filtrer ou de limiter les résultats | |
| 162 | +4. **Vérifiez les autorisations :** Si un outil échoue avec une erreur d'authentification, votre PAT manque peut-être de l'autorisation requise | |
| 163 | + | |
| 164 | +## Problèmes courants | |
| 165 | + | |
| 166 | +### Paramètres requis manquants | |
| 167 | + | |
| 168 | +De nombreux outils GitHub nécessitent des paramètres spécifiques. Vérifiez la description de l'outil pour les champs requis. | |
| 169 | + | |
| 170 | +### Erreurs d'authentification | |
| 171 | + | |
| 172 | +Si vous voyez "missing bearer token" ou "401 Unauthorized" : | |
| 173 | +- Vérifiez que la variable d'environnement `GITHUB_PERSONAL_ACCESS_TOKEN` est définie | |
| 174 | +- Vérifiez que votre token a les autorisations requises | |
| 175 | +- Assurez-vous que le token n'a pas expiré | |
| 176 | + | |
| 177 | +### Limite de taux | |
| 178 | + | |
| 179 | +GitHub a des limites de taux. Si vous les atteignez : | |
| 180 | +- Attendez avant de faire plus de requêtes | |
| 181 | +- Envisagez d'utiliser un PAT à granularité fine avec un accès spécifique au dépôt | |
| 182 | +- Vérifiez l'état de votre limite de taux à https://api.github.com/rate_limit | |
| 183 | + | |
| 184 | +## Résolution des problèmes | |
| 185 | + | |
| 186 | +Si les outils GitHub n'apparaissent pas : | |
| 187 | +1. Vérifiez que MCP est activé dans votre configuration | |
| 188 | +2. Vérifiez que `GITHUB_PERSONAL_ACCESS_TOKEN` est exporté dans votre environnement | |
| 189 | +3. Recherchez les avertissements concernant le serveur GitHub au démarrage | |
| 190 | +4. Testez votre token : `curl -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" https://api.github.com/user` | |
| 191 | +5. Testez le point de terminaison MCP : `curl -X POST https://api.githubcopilot.com/mcp/ -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'` | |
| 192 | + | |
| 193 | +## Voir aussi | |
| 194 | + | |
| 195 | +- [Premiers pas avec MCP](tutorials/premiers-pas.md) | |
| 196 | +- [Utiliser Context7 pour la documentation](utiliser-context7.md) | |
| 197 | +- [Utiliser Wikipedia pour la recherche](utiliser-wikipedia.md) | |
| 198 | +- [Référence de configuration MCP](reference/configuration.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,198 @@ | |||
| 1 | +# Utiliser GitHub pour l'accès au code | ||
| 2 | + | ||
| 3 | +Ce guide montre comment utiliser le serveur MCP GitHub pour interagir avec les dépôts GitHub, les issues, les pull requests, et plus. | ||
| 4 | + | ||
| 5 | +## Prérequis | ||
| 6 | + | ||
| 7 | +- MCP activé dans votre configuration mini-me | ||
| 8 | +- Serveur MCP GitHub configuré | ||
| 9 | +- **Token d'accès personnel GitHub** avec les bonnes autorisations | ||
| 10 | + | ||
| 11 | +## Exemple de configuration | ||
| 12 | + | ||
| 13 | +Ajoutez ceci à votre fichier `agent.yaml` ou votre fichier de configuration personnalisé : | ||
| 14 | + | ||
| 15 | +```yaml | ||
| 16 | +mcp: | ||
| 17 | + enabled: true | ||
| 18 | + servers: | ||
| 19 | + - name: github | ||
| 20 | + transport: http | ||
| 21 | + url: https://api.githubcopilot.com/mcp/ | ||
| 22 | + timeout: 60s | ||
| 23 | + auth: | ||
| 24 | + type: bearer | ||
| 25 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 26 | + headers: | ||
| 27 | + Accept: "application/json, text/event-stream" | ||
| 28 | + # Optionnel : Filtrer les outils à exposer | ||
| 29 | + tools: | ||
| 30 | + - get_me | ||
| 31 | + - list_repositories | ||
| 32 | + - list_issues | ||
| 33 | + - list_pull_requests | ||
| 34 | + - get_file_contents | ||
| 35 | + - search_code | ||
| 36 | + - search_repositories | ||
| 37 | + - create_pull_request | ||
| 38 | + - add_issue_comment | ||
| 39 | +``` | ||
| 40 | + | ||
| 41 | +## Configuration de l'authentification | ||
| 42 | + | ||
| 43 | +1. **Obtenir un PAT GitHub :** | ||
| 44 | + - Allez sur [https://github.com/settings/tokens](https://github.com/settings/tokens) | ||
| 45 | + - Cliquez sur "Generate new token" | ||
| 46 | + - Sélectionnez le type de token "Classic" | ||
| 47 | + | ||
| 48 | +2. **Autorisations requises :** | ||
| 49 | + - Pour l'accès aux dépôts : `repo` (contrôle total des dépôts privés) | ||
| 50 | + - Pour lire le profil utilisateur : `user` (read:user) | ||
| 51 | + - Pour lire les organisations : `read:org` | ||
| 52 | + - Pour les issues et pull requests : `repo` ou `public_repo` | ||
| 53 | + - Pour la recherche de code : `repo` | ||
| 54 | + | ||
| 55 | +3. **Définir la variable d'environnement :** | ||
| 56 | + ```bash | ||
| 57 | + export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_votre_token_ici | ||
| 58 | + ``` | ||
| 59 | + | ||
| 60 | +## Outils disponibles | ||
| 61 | + | ||
| 62 | +Le serveur MCP GitHub fournit de nombreux outils organisés par catégorie : | ||
| 63 | + | ||
| 64 | +### Informations sur le dépôt | ||
| 65 | +- `github_get_me` - Obtenir le profil utilisateur authentifié | ||
| 66 | +- `github_list_repositories` - Lister les dépôts de l'utilisateur | ||
| 67 | +- `github_get_repository` - Obtenir les détails d'un dépôt | ||
| 68 | + | ||
| 69 | +### Accès au code | ||
| 70 | +- `github_get_file_contents` - Obtenir le contenu d'un fichier depuis un dépôt | ||
| 71 | +- `github_list_branches` - Lister les branches d'un dépôt | ||
| 72 | +- `github_list_commits` - Lister les commits d'un dépôt | ||
| 73 | +- `github_get_commit` - Obtenir les détails d'un commit | ||
| 74 | + | ||
| 75 | +### Issues et Pull Requests | ||
| 76 | +- `github_list_issues` - Lister les issues d'un dépôt | ||
| 77 | +- `github_list_pull_requests` - Lister les pull requests d'un dépôt | ||
| 78 | +- `github_create_pull_request` - Créer une nouvelle pull request | ||
| 79 | +- `github_add_issue_comment` - Ajouter un commentaire à une issue | ||
| 80 | +- `github_update_issue_comment` - Mettre à jour un commentaire existant | ||
| 81 | + | ||
| 82 | +### Recherche | ||
| 83 | +- `github_search_code` - Rechercher du code dans les dépôts | ||
| 84 | +- `github_search_issues` - Rechercher des issues avec une correspondance sémantique | ||
| 85 | +- `github_search_pull_requests` - Rechercher des pull requests | ||
| 86 | +- `github_search_repositories` - Rechercher des dépôts | ||
| 87 | +- `github_search_commits` - Rechercher des commits | ||
| 88 | +- `github_search_users` - Rechercher des utilisateurs | ||
| 89 | + | ||
| 90 | +### Gestion de dépôt | ||
| 91 | +- `github_create_repository` - Créer un nouveau dépôt | ||
| 92 | +- `github_create_branch` - Créer une nouvelle branche | ||
| 93 | +- `github_fork_repository` - Forker un dépôt | ||
| 94 | +- `github_merge_pull_request` - Fusionner une pull request | ||
| 95 | +- `github_delete_file` - Supprimer un fichier d'un dépôt | ||
| 96 | + | ||
| 97 | +### Avancé | ||
| 98 | +- `github_run_secret_scanning` - Analyser le contenu à la recherche de secrets | ||
| 99 | +- `github_assign_copilot_to_issue` - Assigner GitHub Copilot à une issue | ||
| 100 | +- `github_get_copilot_job_status` - Obtenir l'état d'un job Copilot | ||
| 101 | +- `github_create_pull_request_with_copilot` - Créer une PR avec l'agent Copilot | ||
| 102 | + | ||
| 103 | +## Exemples d'utilisation | ||
| 104 | + | ||
| 105 | +### Obtenir votre profil | ||
| 106 | + | ||
| 107 | +``` | ||
| 108 | +Utilisez l'outil github_get_me pour obtenir mes informations utilisateur GitHub | ||
| 109 | +``` | ||
| 110 | + | ||
| 111 | +### Lister vos dépôts | ||
| 112 | + | ||
| 113 | +``` | ||
| 114 | +Utilisez l'outil github_list_repositories pour lister tous mes dépôts | ||
| 115 | +``` | ||
| 116 | + | ||
| 117 | +### Rechercher du code | ||
| 118 | + | ||
| 119 | +``` | ||
| 120 | +Utilisez l'outil github_search_code pour trouver des références à "context" dans le dépôt golang/go | ||
| 121 | +``` | ||
| 122 | + | ||
| 123 | +### Obtenir le contenu d'un fichier | ||
| 124 | + | ||
| 125 | +``` | ||
| 126 | +Utilisez l'outil github_get_file_contents pour obtenir le fichier README.md depuis owner/repo sur la branche main | ||
| 127 | +``` | ||
| 128 | + | ||
| 129 | +### Créer une Pull Request | ||
| 130 | + | ||
| 131 | +``` | ||
| 132 | +Utilisez l'outil github_create_pull_request pour créer une PR dans owner/repo avec le titre "Fix bug", head "feature-branch", base "main" | ||
| 133 | +``` | ||
| 134 | + | ||
| 135 | +### Ajouter un commentaire à une issue | ||
| 136 | + | ||
| 137 | +``` | ||
| 138 | +Utilisez l'outil github_add_issue_comment pour ajouter un commentaire "Merci pour le signalement !" à l'issue #123 dans owner/repo | ||
| 139 | +``` | ||
| 140 | + | ||
| 141 | +## Paramètres des outils | ||
| 142 | + | ||
| 143 | +La plupart des outils GitHub nécessitent ces paramètres communs : | ||
| 144 | + | ||
| 145 | +- `owner` - Propriétaire du dépôt (nom d'utilisateur ou organisation) | ||
| 146 | +- `repo` - Nom du dépôt | ||
| 147 | +- Souvent les deux sont marqués avec `x-mcp-header` pour indiquer qu'ils doivent être passés comme en-têtes | ||
| 148 | + | ||
| 149 | +Par exemple : | ||
| 150 | +```json | ||
| 151 | +{ | ||
| 152 | + "owner": "octocat", | ||
| 153 | + "repo": "Hello-World" | ||
| 154 | +} | ||
| 155 | +``` | ||
| 156 | + | ||
| 157 | +## Conseils | ||
| 158 | + | ||
| 159 | +1. **Soyez spécifique avec owner/repo :** Vérifiez toujours le propriétaire et le nom du dépôt avant de continuer | ||
| 160 | +2. **Utilisez des filtres :** Lors de la recherche, utilisez les qualificateurs de recherche GitHub comme `language:go`, `org:`, `user:` | ||
| 161 | +3. **Gérez les grandes réponses :** Certains outils retournent de grandes quantités de données ; envisagez de filtrer ou de limiter les résultats | ||
| 162 | +4. **Vérifiez les autorisations :** Si un outil échoue avec une erreur d'authentification, votre PAT manque peut-être de l'autorisation requise | ||
| 163 | + | ||
| 164 | +## Problèmes courants | ||
| 165 | + | ||
| 166 | +### Paramètres requis manquants | ||
| 167 | + | ||
| 168 | +De nombreux outils GitHub nécessitent des paramètres spécifiques. Vérifiez la description de l'outil pour les champs requis. | ||
| 169 | + | ||
| 170 | +### Erreurs d'authentification | ||
| 171 | + | ||
| 172 | +Si vous voyez "missing bearer token" ou "401 Unauthorized" : | ||
| 173 | +- Vérifiez que la variable d'environnement `GITHUB_PERSONAL_ACCESS_TOKEN` est définie | ||
| 174 | +- Vérifiez que votre token a les autorisations requises | ||
| 175 | +- Assurez-vous que le token n'a pas expiré | ||
| 176 | + | ||
| 177 | +### Limite de taux | ||
| 178 | + | ||
| 179 | +GitHub a des limites de taux. Si vous les atteignez : | ||
| 180 | +- Attendez avant de faire plus de requêtes | ||
| 181 | +- Envisagez d'utiliser un PAT à granularité fine avec un accès spécifique au dépôt | ||
| 182 | +- Vérifiez l'état de votre limite de taux à https://api.github.com/rate_limit | ||
| 183 | + | ||
| 184 | +## Résolution des problèmes | ||
| 185 | + | ||
| 186 | +Si les outils GitHub n'apparaissent pas : | ||
| 187 | +1. Vérifiez que MCP est activé dans votre configuration | ||
| 188 | +2. Vérifiez que `GITHUB_PERSONAL_ACCESS_TOKEN` est exporté dans votre environnement | ||
| 189 | +3. Recherchez les avertissements concernant le serveur GitHub au démarrage | ||
| 190 | +4. Testez votre token : `curl -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" https://api.github.com/user` | ||
| 191 | +5. Testez le point de terminaison MCP : `curl -X POST https://api.githubcopilot.com/mcp/ -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'` | ||
| 192 | + | ||
| 193 | +## Voir aussi | ||
| 194 | + | ||
| 195 | +- [Premiers pas avec MCP](tutorials/premiers-pas.md) | ||
| 196 | +- [Utiliser Context7 pour la documentation](utiliser-context7.md) | ||
| 197 | +- [Utiliser Wikipedia pour la recherche](utiliser-wikipedia.md) | ||
| 198 | +- [Référence de configuration MCP](reference/configuration.md) | ||
added
docs/fr/how-to/mcp-utiliser-wikipedia.md +105 -0 | new file mode 100644 | ||
| @@ -0,0 +1,105 @@ | ||
| 1 | +# Utiliser Wikipedia pour la recherche | |
| 2 | + | |
| 3 | +Ce guide montre comment utiliser le serveur MCP Wikipedia pour rechercher et récupérer des informations depuis Wikipedia. | |
| 4 | + | |
| 5 | +## Prérequis | |
| 6 | + | |
| 7 | +- MCP activé dans votre configuration mini-me | |
| 8 | +- Serveur MCP Wikipedia configuré (aucune authentification requise) | |
| 9 | + | |
| 10 | +## Exemple de configuration | |
| 11 | + | |
| 12 | +Ajoutez ceci à votre fichier `agent.yaml` ou votre fichier de configuration personnalisé : | |
| 13 | + | |
| 14 | +```yaml | |
| 15 | +mcp: | |
| 16 | + enabled: true | |
| 17 | + servers: | |
| 18 | + - name: wikipedia | |
| 19 | + transport: http | |
| 20 | + url: https://wikipedia-mcp.vercel.app/mcp | |
| 21 | + timeout: 30s | |
| 22 | + auth: | |
| 23 | + type: none | |
| 24 | +``` | |
| 25 | + | |
| 26 | +## Outils disponibles | |
| 27 | + | |
| 28 | +Le serveur MCP Wikipedia fournit ces outils : | |
| 29 | + | |
| 30 | +| Outil | Description | | |
| 31 | +|-------|-------------| | |
| 32 | +| `wikipedia_search_wikipedia` | Rechercher des articles Wikipedia par requête | | |
| 33 | +| `wikipedia_get_article` | Obtenir le contenu complet d'un article par titre | | |
| 34 | +| `wikipedia_get_summary` | Obtenir un résumé d'article par titre | | |
| 35 | +| `wikipedia_get_sections` | Obtenir les sections/plan d'un article par titre | | |
| 36 | +| `wikipedia_get_links` | Obtenir les liens d'un article | | |
| 37 | +| `wikipedia_get_coordinates` | Obtenir les coordonnées géographiques depuis un article | | |
| 38 | +| `wikipedia_get_related_topics` | Obtenir les sujets liés à un article | | |
| 39 | +| `wikipedia_summarize_article_for_query` | Résumer les sections d'article pertinentes pour une requête | | |
| 40 | +| `wikipedia_summarize_article_section` | Résumer une section spécifique | | |
| 41 | +| `wikipedia_extract_key_facts` | Extraire les faits clés d'un article | | |
| 42 | +| `wikipedia_test_wikipedia_connectivity` | Tester la connexion à Wikipedia | | |
| 43 | +| `wikipedia_list_supported_countries` | Lister les éditions Wikipedia prises en charge | | |
| 44 | +| `wikipedia_multi_search_wikipedia` | Rechercher dans plusieurs éditions Wikipedia | | |
| 45 | + | |
| 46 | +## Exemples d'utilisation | |
| 47 | + | |
| 48 | +### Rechercher des articles | |
| 49 | + | |
| 50 | +``` | |
| 51 | +Utilisez l'outil wikipedia_search_wikipedia pour trouver des articles sur "l'intelligence artificielle" | |
| 52 | +``` | |
| 53 | + | |
| 54 | +### Obtenir le contenu d'un article | |
| 55 | + | |
| 56 | +``` | |
| 57 | +Utilisez l'outil wikipedia_get_article pour récupérer le contenu complet de "Go (langage de programmation)" | |
| 58 | +``` | |
| 59 | + | |
| 60 | +### Obtenir un résumé d'article | |
| 61 | + | |
| 62 | +``` | |
| 63 | +Utilisez l'outil wikipedia_get_summary pour obtenir un résumé bref de "Apprentissage automatique" | |
| 64 | +``` | |
| 65 | + | |
| 66 | +### Obtenir la structure d'un article | |
| 67 | + | |
| 68 | +``` | |
| 69 | +Utilisez l'outil wikipedia_get_sections pour voir le plan de "Python (langage de programmation)" | |
| 70 | +``` | |
| 71 | + | |
| 72 | +## Conseils | |
| 73 | + | |
| 74 | +- Les recherches Wikipedia ne sont pas sensibles à la casse | |
| 75 | +- Utilisez des requêtes spécifiques pour de meilleurs résultats | |
| 76 | +- Pour les sujets de programmation, essayez d'inclure "langage de programmation", "framework" ou "bibliothèque" dans votre requête | |
| 77 | +- Le serveur MCP Wikipedia prend en charge plusieurs éditions linguistiques | |
| 78 | + | |
| 79 | +## Prise en charge des langues | |
| 80 | + | |
| 81 | +Pour rechercher dans une édition spécifique de Wikipedia, vous pouvez : | |
| 82 | + | |
| 83 | +1. Utiliser l'outil `wikipedia_multi_search_wikipedia` avec un paramètre de langue | |
| 84 | +2. Spécifier la langue dans votre requête (ex: "Python site:fr.wikipedia.org") | |
| 85 | + | |
| 86 | +## Limites de taux | |
| 87 | + | |
| 88 | +Le serveur MCP Wikipedia a des limites de taux raisonnables. Pour une utilisation intensive, envisagez : | |
| 89 | +- Mettre en cache les résultats localement | |
| 90 | +- Utiliser l'outil avec discernement | |
| 91 | +- Consulter la documentation des limites de taux du serveur | |
| 92 | + | |
| 93 | +## Résolution des problèmes | |
| 94 | + | |
| 95 | +Si les outils n'apparaissent pas : | |
| 96 | +1. Vérifiez que MCP est activé dans votre configuration | |
| 97 | +2. Vérifiez que le serveur Wikipedia est correctement configuré | |
| 98 | +3. Assurez-vous qu'il n'y a pas d'avertissements concernant le serveur Wikipedia au démarrage | |
| 99 | +4. Vérifiez que votre connexion réseau permet l'accès à `wikipedia-mcp.vercel.app` | |
| 100 | + | |
| 101 | +## Voir aussi | |
| 102 | + | |
| 103 | +- [Premiers pas avec MCP](tutorials/premiers-pas.md) | |
| 104 | +- [Utiliser Context7 pour la documentation](utiliser-context7.md) | |
| 105 | +- [Utiliser GitHub pour l'accès au code](utiliser-github.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | +# Utiliser Wikipedia pour la recherche | ||
| 2 | + | ||
| 3 | +Ce guide montre comment utiliser le serveur MCP Wikipedia pour rechercher et récupérer des informations depuis Wikipedia. | ||
| 4 | + | ||
| 5 | +## Prérequis | ||
| 6 | + | ||
| 7 | +- MCP activé dans votre configuration mini-me | ||
| 8 | +- Serveur MCP Wikipedia configuré (aucune authentification requise) | ||
| 9 | + | ||
| 10 | +## Exemple de configuration | ||
| 11 | + | ||
| 12 | +Ajoutez ceci à votre fichier `agent.yaml` ou votre fichier de configuration personnalisé : | ||
| 13 | + | ||
| 14 | +```yaml | ||
| 15 | +mcp: | ||
| 16 | + enabled: true | ||
| 17 | + servers: | ||
| 18 | + - name: wikipedia | ||
| 19 | + transport: http | ||
| 20 | + url: https://wikipedia-mcp.vercel.app/mcp | ||
| 21 | + timeout: 30s | ||
| 22 | + auth: | ||
| 23 | + type: none | ||
| 24 | +``` | ||
| 25 | + | ||
| 26 | +## Outils disponibles | ||
| 27 | + | ||
| 28 | +Le serveur MCP Wikipedia fournit ces outils : | ||
| 29 | + | ||
| 30 | +| Outil | Description | | ||
| 31 | +|-------|-------------| | ||
| 32 | +| `wikipedia_search_wikipedia` | Rechercher des articles Wikipedia par requête | | ||
| 33 | +| `wikipedia_get_article` | Obtenir le contenu complet d'un article par titre | | ||
| 34 | +| `wikipedia_get_summary` | Obtenir un résumé d'article par titre | | ||
| 35 | +| `wikipedia_get_sections` | Obtenir les sections/plan d'un article par titre | | ||
| 36 | +| `wikipedia_get_links` | Obtenir les liens d'un article | | ||
| 37 | +| `wikipedia_get_coordinates` | Obtenir les coordonnées géographiques depuis un article | | ||
| 38 | +| `wikipedia_get_related_topics` | Obtenir les sujets liés à un article | | ||
| 39 | +| `wikipedia_summarize_article_for_query` | Résumer les sections d'article pertinentes pour une requête | | ||
| 40 | +| `wikipedia_summarize_article_section` | Résumer une section spécifique | | ||
| 41 | +| `wikipedia_extract_key_facts` | Extraire les faits clés d'un article | | ||
| 42 | +| `wikipedia_test_wikipedia_connectivity` | Tester la connexion à Wikipedia | | ||
| 43 | +| `wikipedia_list_supported_countries` | Lister les éditions Wikipedia prises en charge | | ||
| 44 | +| `wikipedia_multi_search_wikipedia` | Rechercher dans plusieurs éditions Wikipedia | | ||
| 45 | + | ||
| 46 | +## Exemples d'utilisation | ||
| 47 | + | ||
| 48 | +### Rechercher des articles | ||
| 49 | + | ||
| 50 | +``` | ||
| 51 | +Utilisez l'outil wikipedia_search_wikipedia pour trouver des articles sur "l'intelligence artificielle" | ||
| 52 | +``` | ||
| 53 | + | ||
| 54 | +### Obtenir le contenu d'un article | ||
| 55 | + | ||
| 56 | +``` | ||
| 57 | +Utilisez l'outil wikipedia_get_article pour récupérer le contenu complet de "Go (langage de programmation)" | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +### Obtenir un résumé d'article | ||
| 61 | + | ||
| 62 | +``` | ||
| 63 | +Utilisez l'outil wikipedia_get_summary pour obtenir un résumé bref de "Apprentissage automatique" | ||
| 64 | +``` | ||
| 65 | + | ||
| 66 | +### Obtenir la structure d'un article | ||
| 67 | + | ||
| 68 | +``` | ||
| 69 | +Utilisez l'outil wikipedia_get_sections pour voir le plan de "Python (langage de programmation)" | ||
| 70 | +``` | ||
| 71 | + | ||
| 72 | +## Conseils | ||
| 73 | + | ||
| 74 | +- Les recherches Wikipedia ne sont pas sensibles à la casse | ||
| 75 | +- Utilisez des requêtes spécifiques pour de meilleurs résultats | ||
| 76 | +- Pour les sujets de programmation, essayez d'inclure "langage de programmation", "framework" ou "bibliothèque" dans votre requête | ||
| 77 | +- Le serveur MCP Wikipedia prend en charge plusieurs éditions linguistiques | ||
| 78 | + | ||
| 79 | +## Prise en charge des langues | ||
| 80 | + | ||
| 81 | +Pour rechercher dans une édition spécifique de Wikipedia, vous pouvez : | ||
| 82 | + | ||
| 83 | +1. Utiliser l'outil `wikipedia_multi_search_wikipedia` avec un paramètre de langue | ||
| 84 | +2. Spécifier la langue dans votre requête (ex: "Python site:fr.wikipedia.org") | ||
| 85 | + | ||
| 86 | +## Limites de taux | ||
| 87 | + | ||
| 88 | +Le serveur MCP Wikipedia a des limites de taux raisonnables. Pour une utilisation intensive, envisagez : | ||
| 89 | +- Mettre en cache les résultats localement | ||
| 90 | +- Utiliser l'outil avec discernement | ||
| 91 | +- Consulter la documentation des limites de taux du serveur | ||
| 92 | + | ||
| 93 | +## Résolution des problèmes | ||
| 94 | + | ||
| 95 | +Si les outils n'apparaissent pas : | ||
| 96 | +1. Vérifiez que MCP est activé dans votre configuration | ||
| 97 | +2. Vérifiez que le serveur Wikipedia est correctement configuré | ||
| 98 | +3. Assurez-vous qu'il n'y a pas d'avertissements concernant le serveur Wikipedia au démarrage | ||
| 99 | +4. Vérifiez que votre connexion réseau permet l'accès à `wikipedia-mcp.vercel.app` | ||
| 100 | + | ||
| 101 | +## Voir aussi | ||
| 102 | + | ||
| 103 | +- [Premiers pas avec MCP](tutorials/premiers-pas.md) | ||
| 104 | +- [Utiliser Context7 pour la documentation](utiliser-context7.md) | ||
| 105 | +- [Utiliser GitHub pour l'accès au code](utiliser-github.md) | ||
added
docs/fr/reference/mcp-authentification.md +276 -0 | new file mode 100644 | ||
| @@ -0,0 +1,276 @@ | ||
| 1 | +# Référence de l'authentification MCP | |
| 2 | + | |
| 3 | +Ce document décrit les méthodes d'authentification prises en charge par le client MCP de mini-me. | |
| 4 | + | |
| 5 | +## Aperçu | |
| 6 | + | |
| 7 | +Les serveurs MCP peuvent nécessiter différentes méthodes d'authentification. Mini-me prend actuellement en charge : | |
| 8 | + | |
| 9 | +- **`none`** : Aucune authentification (serveurs publics) | |
| 10 | +- **`bearer`** : Authentification par token Bearer (en-tête HTTP Authorization) | |
| 11 | +- **`api_key`** : Authentification par clé API (en-tête personnalisé) | |
| 12 | + | |
| 13 | +## Types d'authentification | |
| 14 | + | |
| 15 | +### Type : `none` | |
| 16 | + | |
| 17 | +**Cas d'utilisation :** Serveurs MCP publics qui ne nécessitent pas d'authentification. | |
| 18 | + | |
| 19 | +**Configuration :** | |
| 20 | +```yaml | |
| 21 | +servers: | |
| 22 | + - name: wikipedia | |
| 23 | + transport: http | |
| 24 | + url: https://wikipedia-mcp.vercel.app/mcp | |
| 25 | + auth: | |
| 26 | + type: none | |
| 27 | +``` | |
| 28 | + | |
| 29 | +**Comportement :** Aucun en-tête d'authentification n'est ajouté aux requêtes. | |
| 30 | + | |
| 31 | +--- | |
| 32 | + | |
| 33 | +### Type : `bearer` | |
| 34 | + | |
| 35 | +**Cas d'utilisation :** Serveurs MCP qui utilisent l'authentification par token Bearer (le plus courant). | |
| 36 | + | |
| 37 | +**Configuration :** | |
| 38 | +```yaml | |
| 39 | +servers: | |
| 40 | + - name: github | |
| 41 | + transport: http | |
| 42 | + url: https://api.githubcopilot.com/mcp/ | |
| 43 | + auth: | |
| 44 | + type: bearer | |
| 45 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 46 | +``` | |
| 47 | + | |
| 48 | +**Requis :** `tokenEnv` - Nom de la variable d'environnement contenant le token. | |
| 49 | + | |
| 50 | +**Comportement :** Ajoute l'en-tête `Authorization: Bearer {token}` à toutes les requêtes. | |
| 51 | + | |
| 52 | +**En-tête HTTP :** | |
| 53 | +``` | |
| 54 | +Authorization: Bearer ghp_votre_token_ici | |
| 55 | +``` | |
| 56 | + | |
| 57 | +**Serveurs pris en charge :** | |
| 58 | +- GitHub MCP Server (`api.githubcopilot.com`) | |
| 59 | +- Context7 MCP Server (`mcp.context7.com`) | |
| 60 | +- Tout serveur acceptant l'authentification par token Bearer | |
| 61 | + | |
| 62 | +--- | |
| 63 | + | |
| 64 | +### Type : `api_key` | |
| 65 | + | |
| 66 | +**Cas d'utilisation :** Serveurs MCP qui utilisent l'authentification par clé API avec des en-têtes personnalisés. | |
| 67 | + | |
| 68 | +**Configuration :** | |
| 69 | +```yaml | |
| 70 | +servers: | |
| 71 | + - name: duckduckgo | |
| 72 | + transport: http | |
| 73 | + url: https://mcp.hasdata.com/mcp | |
| 74 | + auth: | |
| 75 | + type: api_key | |
| 76 | + apiKeyEnv: HASDATA_API_KEY | |
| 77 | + apiKeyHeader: x-api-key | |
| 78 | +``` | |
| 79 | + | |
| 80 | +**Requis :** | |
| 81 | +- `apiKeyEnv` - Nom de la variable d'environnement contenant la clé API | |
| 82 | +- `apiKeyHeader` - Nom de l'en-tête HTTP à utiliser | |
| 83 | + | |
| 84 | +**Optionnel :** Si `apiKeyHeader` n'est pas spécifié, la valeur par défaut est `Authorization`. | |
| 85 | + | |
| 86 | +**Comportement :** Ajoute l'en-tête `{apiKeyHeader}: {api_key}` à toutes les requêtes. | |
| 87 | + | |
| 88 | +**En-tête HTTP :** | |
| 89 | +``` | |
| 90 | +X-API-Key: votre_clé_api_ici | |
| 91 | +``` | |
| 92 | + | |
| 93 | +**Serveurs pris en charge :** | |
| 94 | +- DuckDuckGo MCP Server via HasData | |
| 95 | + | |
| 96 | +--- | |
| 97 | + | |
| 98 | +## Non encore pris en charge | |
| 99 | + | |
| 100 | +### OAuth 2.0 | |
| 101 | + | |
| 102 | +**Statut :** Non encore implémenté dans mini-me. | |
| 103 | + | |
| 104 | +**Cas d'utilisation :** Serveurs MCP qui nécessitent un flux d'authentification OAuth 2.0 (connexion via navigateur). | |
| 105 | + | |
| 106 | +**Exemple de serveur :** Certains serveurs MCP personnalisés qui implémentent OAuth. | |
| 107 | + | |
| 108 | +**Solution de contournement :** Utilisez un serveur MCP auto-hébergé qui accepte des tokens Bearer ou des clés API. | |
| 109 | + | |
| 110 | +--- | |
| 111 | + | |
| 112 | +## Variables d'environnement | |
| 113 | + | |
| 114 | +Les tokens d'authentification et les clés API sont chargés à partir de variables d'environnement. C'est une bonne pratique de sécurité qui empêche les secrets d'être commités dans le contrôle de version. | |
| 115 | + | |
| 116 | +### Définir les variables d'environnement | |
| 117 | + | |
| 118 | +**Linux/macOS :** | |
| 119 | +```bash | |
| 120 | +# Temporaire (session actuelle) | |
| 121 | +export GITHUB_PERSONAL_ACCESS_TOKEN="ghp_votre_token_ici" | |
| 122 | + | |
| 123 | +# Permanent (ajouter à ~/.bashrc ou ~/.zshrc) | |
| 124 | +echo 'export GITHUB_PERSONAL_ACCESS_TOKEN="ghp_votre_token_ici"' >> ~/.bashrc | |
| 125 | +source ~/.bashrc | |
| 126 | + | |
| 127 | +# Commande unique | |
| 128 | +GITHUB_PERSONAL_ACCESS_TOKEN="ghp_votre_token_ici" mm --config votre_config.yaml | |
| 129 | +``` | |
| 130 | + | |
| 131 | +**Windows (Invite de commandes) :** | |
| 132 | +```cmd | |
| 133 | +set GITHUB_PERSONAL_ACCESS_TOKEN=ghp_votre_token_ici | |
| 134 | +mm --config votre_config.yaml | |
| 135 | +``` | |
| 136 | + | |
| 137 | +**Windows (PowerShell) :** | |
| 138 | +```powershell | |
| 139 | +$env:GITHUB_PERSONAL_ACCESS_TOKEN="ghp_votre_token_ici" | |
| 140 | +mm --config votre_config.yaml | |
| 141 | +``` | |
| 142 | + | |
| 143 | +### Bonnes pratiques | |
| 144 | + | |
| 145 | +1. **Ne jamais coder en dur les secrets :** Utilisez toujours des variables d'environnement ou des systèmes de gestion de secrets | |
| 146 | +2. **Utilisez les fichiers `.env` avec prudence :** Si vous utilisez des fichiers `.env`, ajoutez-les à `.gitignore` | |
| 147 | +3. **Limitez les permissions :** Utilisez des tokens/clés API avec les permissions minimales requises | |
| 148 | +4. **Rotation régulière :** Faites une rotation régulière des tokens et clés API | |
| 149 | +5. **Audit des accès :** Passez régulièrement en revue quels services ont accès à vos tokens | |
| 150 | + | |
| 151 | +--- | |
| 152 | + | |
| 153 | +## Exigences spécifiques aux serveurs | |
| 154 | + | |
| 155 | +### GitHub MCP Server | |
| 156 | + | |
| 157 | +**Point de terminaison :** `https://api.githubcopilot.com/mcp/` | |
| 158 | + | |
| 159 | +**Authentification :** Token Bearer (`bearer` type) | |
| 160 | + | |
| 161 | +**Autorisations requises :** | |
| 162 | +- `read:user` - Pour l'accès au profil utilisateur (`get_me`) | |
| 163 | +- `repo` - Pour l'accès aux dépôts (la plupart des outils) | |
| 164 | +- `read:org` - Pour l'accès aux organisations | |
| 165 | +- `user` - Informations de base de l'utilisateur | |
| 166 | + | |
| 167 | +**Type de token :** Classic Personal Access Token (PAT) | |
| 168 | + | |
| 169 | +**Génération du token :** | |
| 170 | +1. Allez sur [https://github.com/settings/tokens](https://github.com/settings/tokens) | |
| 171 | +2. Cliquez sur "Generate new token (classic)" | |
| 172 | +3. Sélectionnez les autorisations requises | |
| 173 | +4. Générez le token | |
| 174 | +5. Copiez le token (il ne sera plus affiché) | |
| 175 | + | |
| 176 | +--- | |
| 177 | + | |
| 178 | +### Context7 MCP Server | |
| 179 | + | |
| 180 | +**Point de terminaison :** `https://mcp.context7.com/mcp` | |
| 181 | + | |
| 182 | +**Authentification :** Token Bearer (`bearer` type) | |
| 183 | + | |
| 184 | +**Token :** Clé API Context7 | |
| 185 | + | |
| 186 | +**Génération du token :** | |
| 187 | +1. Inscrivez-vous sur [https://context7.com](https://context7.com) | |
| 188 | +2. Obtenez votre clé API depuis le tableau de bord | |
| 189 | +3. Définissez-la comme variable d'environnement `CONTEXT7_API_KEY` | |
| 190 | + | |
| 191 | +--- | |
| 192 | + | |
| 193 | +### Wikipedia MCP Server | |
| 194 | + | |
| 195 | +**Point de terminaison :** `https://wikipedia-mcp.vercel.app/mcp` | |
| 196 | + | |
| 197 | +**Authentification :** Aucune (`none` type) | |
| 198 | + | |
| 199 | +**Token :** Non requis | |
| 200 | + | |
| 201 | +--- | |
| 202 | + | |
| 203 | +### HasData (DuckDuckGo) MCP Server | |
| 204 | + | |
| 205 | +**Point de terminaison :** `https://mcp.hasdata.com/mcp?apis=duckduckgo` | |
| 206 | + | |
| 207 | +**Authentification :** Clé API (`api_key` type) | |
| 208 | + | |
| 209 | +**Token :** Clé API HasData | |
| 210 | + | |
| 211 | +**En-têtes :** | |
| 212 | +- Nom de l'en-tête : `x-api-key` | |
| 213 | +- Variable d'environnement : `HASDATA_API_KEY` | |
| 214 | + | |
| 215 | +--- | |
| 216 | + | |
| 217 | +## Résolution des problèmes | |
| 218 | + | |
| 219 | +### Erreur "missing bearer token" ou "missing API key" | |
| 220 | + | |
| 221 | +**Cause :** La variable d'environnement requise n'est pas définie ou est vide. | |
| 222 | + | |
| 223 | +**Solution :** | |
| 224 | +1. Vérifiez que la variable est définie : `echo $NOM_VARIABLE` | |
| 225 | +2. Vérifiez qu'elle est exportée : `env | grep NOM_VARIABLE` | |
| 226 | +3. Vérifiez les fautes de frappe dans le nom de la variable dans votre config | |
| 227 | + | |
| 228 | +### L'authentification échoue mais le token fonctionne avec curl | |
| 229 | + | |
| 230 | +**Cause :** Exigences d'authentification différentes entre l'API REST et le point de terminaison MCP. | |
| 231 | + | |
| 232 | +**Exemple :** API REST GitHub (`api.github.com`) vs MCP GitHub (`api.githubcopilot.com`) | |
| 233 | + | |
| 234 | +**Solution :** | |
| 235 | +- Testez directement avec le point de terminaison MCP : | |
| 236 | + ```bash | |
| 237 | + curl -X POST https://api.githubcopilot.com/mcp/ \ | |
| 238 | + -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \ | |
| 239 | + -H "Accept: application/json, text/event-stream" \ | |
| 240 | + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | |
| 241 | + ``` | |
| 242 | +- Assurez-vous que votre token a les autorisations requises pour le serveur MCP | |
| 243 | + | |
| 244 | +### Le serveur retourne 403 Forbidden | |
| 245 | + | |
| 246 | +**Cause :** Le token manque des permissions requises ou des exigences spécifiques au serveur. | |
| 247 | + | |
| 248 | +**Solution :** | |
| 249 | +- Vérifiez si le token a expiré | |
| 250 | +- Vérifiez que le token a les bonnes autorisations | |
| 251 | +- Certains serveurs peuvent nécessiter des en-têtes ou paramètres supplémentaires | |
| 252 | + | |
| 253 | +### La connexion au serveur expire | |
| 254 | + | |
| 255 | +**Solution :** | |
| 256 | +- Augmentez le `timeout` dans votre configuration de serveur | |
| 257 | +- Vérifiez la connectivité réseau vers le point de terminaison du serveur MCP | |
| 258 | +- Vérifiez que le serveur est en ligne et accessible | |
| 259 | + | |
| 260 | +--- | |
| 261 | + | |
| 262 | +## Considérations de sécurité | |
| 263 | + | |
| 264 | +1. **Portée du token :** N'accordez que les autorisations minimales requises | |
| 265 | +2. **Stockage du token :** Utilisez des variables d'environnement, pas des fichiers de configuration | |
| 266 | +3. **Partage du token :** Ne partagez jamais les tokens ni ne les commitez dans le contrôle de version | |
| 267 | +4. **Rotation du token :** Faites une rotation régulière des tokens, surtout s'ils sont compromis | |
| 268 | +5. **Sécurité réseau :** Assurez-vous que les connexions aux serveurs MCP utilisent HTTPS | |
| 269 | + | |
| 270 | +--- | |
| 271 | + | |
| 272 | +## Voir aussi | |
| 273 | + | |
| 274 | +- [Référence de configuration MCP](configuration.md) | |
| 275 | +- [Outils MCP disponibles](outils-disponibles.md) | |
| 276 | +- [Architecture MCP](explanation/architecture.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,276 @@ | |||
| 1 | +# Référence de l'authentification MCP | ||
| 2 | + | ||
| 3 | +Ce document décrit les méthodes d'authentification prises en charge par le client MCP de mini-me. | ||
| 4 | + | ||
| 5 | +## Aperçu | ||
| 6 | + | ||
| 7 | +Les serveurs MCP peuvent nécessiter différentes méthodes d'authentification. Mini-me prend actuellement en charge : | ||
| 8 | + | ||
| 9 | +- **`none`** : Aucune authentification (serveurs publics) | ||
| 10 | +- **`bearer`** : Authentification par token Bearer (en-tête HTTP Authorization) | ||
| 11 | +- **`api_key`** : Authentification par clé API (en-tête personnalisé) | ||
| 12 | + | ||
| 13 | +## Types d'authentification | ||
| 14 | + | ||
| 15 | +### Type : `none` | ||
| 16 | + | ||
| 17 | +**Cas d'utilisation :** Serveurs MCP publics qui ne nécessitent pas d'authentification. | ||
| 18 | + | ||
| 19 | +**Configuration :** | ||
| 20 | +```yaml | ||
| 21 | +servers: | ||
| 22 | + - name: wikipedia | ||
| 23 | + transport: http | ||
| 24 | + url: https://wikipedia-mcp.vercel.app/mcp | ||
| 25 | + auth: | ||
| 26 | + type: none | ||
| 27 | +``` | ||
| 28 | + | ||
| 29 | +**Comportement :** Aucun en-tête d'authentification n'est ajouté aux requêtes. | ||
| 30 | + | ||
| 31 | +--- | ||
| 32 | + | ||
| 33 | +### Type : `bearer` | ||
| 34 | + | ||
| 35 | +**Cas d'utilisation :** Serveurs MCP qui utilisent l'authentification par token Bearer (le plus courant). | ||
| 36 | + | ||
| 37 | +**Configuration :** | ||
| 38 | +```yaml | ||
| 39 | +servers: | ||
| 40 | + - name: github | ||
| 41 | + transport: http | ||
| 42 | + url: https://api.githubcopilot.com/mcp/ | ||
| 43 | + auth: | ||
| 44 | + type: bearer | ||
| 45 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 46 | +``` | ||
| 47 | + | ||
| 48 | +**Requis :** `tokenEnv` - Nom de la variable d'environnement contenant le token. | ||
| 49 | + | ||
| 50 | +**Comportement :** Ajoute l'en-tête `Authorization: Bearer {token}` à toutes les requêtes. | ||
| 51 | + | ||
| 52 | +**En-tête HTTP :** | ||
| 53 | +``` | ||
| 54 | +Authorization: Bearer ghp_votre_token_ici | ||
| 55 | +``` | ||
| 56 | + | ||
| 57 | +**Serveurs pris en charge :** | ||
| 58 | +- GitHub MCP Server (`api.githubcopilot.com`) | ||
| 59 | +- Context7 MCP Server (`mcp.context7.com`) | ||
| 60 | +- Tout serveur acceptant l'authentification par token Bearer | ||
| 61 | + | ||
| 62 | +--- | ||
| 63 | + | ||
| 64 | +### Type : `api_key` | ||
| 65 | + | ||
| 66 | +**Cas d'utilisation :** Serveurs MCP qui utilisent l'authentification par clé API avec des en-têtes personnalisés. | ||
| 67 | + | ||
| 68 | +**Configuration :** | ||
| 69 | +```yaml | ||
| 70 | +servers: | ||
| 71 | + - name: duckduckgo | ||
| 72 | + transport: http | ||
| 73 | + url: https://mcp.hasdata.com/mcp | ||
| 74 | + auth: | ||
| 75 | + type: api_key | ||
| 76 | + apiKeyEnv: HASDATA_API_KEY | ||
| 77 | + apiKeyHeader: x-api-key | ||
| 78 | +``` | ||
| 79 | + | ||
| 80 | +**Requis :** | ||
| 81 | +- `apiKeyEnv` - Nom de la variable d'environnement contenant la clé API | ||
| 82 | +- `apiKeyHeader` - Nom de l'en-tête HTTP à utiliser | ||
| 83 | + | ||
| 84 | +**Optionnel :** Si `apiKeyHeader` n'est pas spécifié, la valeur par défaut est `Authorization`. | ||
| 85 | + | ||
| 86 | +**Comportement :** Ajoute l'en-tête `{apiKeyHeader}: {api_key}` à toutes les requêtes. | ||
| 87 | + | ||
| 88 | +**En-tête HTTP :** | ||
| 89 | +``` | ||
| 90 | +X-API-Key: votre_clé_api_ici | ||
| 91 | +``` | ||
| 92 | + | ||
| 93 | +**Serveurs pris en charge :** | ||
| 94 | +- DuckDuckGo MCP Server via HasData | ||
| 95 | + | ||
| 96 | +--- | ||
| 97 | + | ||
| 98 | +## Non encore pris en charge | ||
| 99 | + | ||
| 100 | +### OAuth 2.0 | ||
| 101 | + | ||
| 102 | +**Statut :** Non encore implémenté dans mini-me. | ||
| 103 | + | ||
| 104 | +**Cas d'utilisation :** Serveurs MCP qui nécessitent un flux d'authentification OAuth 2.0 (connexion via navigateur). | ||
| 105 | + | ||
| 106 | +**Exemple de serveur :** Certains serveurs MCP personnalisés qui implémentent OAuth. | ||
| 107 | + | ||
| 108 | +**Solution de contournement :** Utilisez un serveur MCP auto-hébergé qui accepte des tokens Bearer ou des clés API. | ||
| 109 | + | ||
| 110 | +--- | ||
| 111 | + | ||
| 112 | +## Variables d'environnement | ||
| 113 | + | ||
| 114 | +Les tokens d'authentification et les clés API sont chargés à partir de variables d'environnement. C'est une bonne pratique de sécurité qui empêche les secrets d'être commités dans le contrôle de version. | ||
| 115 | + | ||
| 116 | +### Définir les variables d'environnement | ||
| 117 | + | ||
| 118 | +**Linux/macOS :** | ||
| 119 | +```bash | ||
| 120 | +# Temporaire (session actuelle) | ||
| 121 | +export GITHUB_PERSONAL_ACCESS_TOKEN="ghp_votre_token_ici" | ||
| 122 | + | ||
| 123 | +# Permanent (ajouter à ~/.bashrc ou ~/.zshrc) | ||
| 124 | +echo 'export GITHUB_PERSONAL_ACCESS_TOKEN="ghp_votre_token_ici"' >> ~/.bashrc | ||
| 125 | +source ~/.bashrc | ||
| 126 | + | ||
| 127 | +# Commande unique | ||
| 128 | +GITHUB_PERSONAL_ACCESS_TOKEN="ghp_votre_token_ici" mm --config votre_config.yaml | ||
| 129 | +``` | ||
| 130 | + | ||
| 131 | +**Windows (Invite de commandes) :** | ||
| 132 | +```cmd | ||
| 133 | +set GITHUB_PERSONAL_ACCESS_TOKEN=ghp_votre_token_ici | ||
| 134 | +mm --config votre_config.yaml | ||
| 135 | +``` | ||
| 136 | + | ||
| 137 | +**Windows (PowerShell) :** | ||
| 138 | +```powershell | ||
| 139 | +$env:GITHUB_PERSONAL_ACCESS_TOKEN="ghp_votre_token_ici" | ||
| 140 | +mm --config votre_config.yaml | ||
| 141 | +``` | ||
| 142 | + | ||
| 143 | +### Bonnes pratiques | ||
| 144 | + | ||
| 145 | +1. **Ne jamais coder en dur les secrets :** Utilisez toujours des variables d'environnement ou des systèmes de gestion de secrets | ||
| 146 | +2. **Utilisez les fichiers `.env` avec prudence :** Si vous utilisez des fichiers `.env`, ajoutez-les à `.gitignore` | ||
| 147 | +3. **Limitez les permissions :** Utilisez des tokens/clés API avec les permissions minimales requises | ||
| 148 | +4. **Rotation régulière :** Faites une rotation régulière des tokens et clés API | ||
| 149 | +5. **Audit des accès :** Passez régulièrement en revue quels services ont accès à vos tokens | ||
| 150 | + | ||
| 151 | +--- | ||
| 152 | + | ||
| 153 | +## Exigences spécifiques aux serveurs | ||
| 154 | + | ||
| 155 | +### GitHub MCP Server | ||
| 156 | + | ||
| 157 | +**Point de terminaison :** `https://api.githubcopilot.com/mcp/` | ||
| 158 | + | ||
| 159 | +**Authentification :** Token Bearer (`bearer` type) | ||
| 160 | + | ||
| 161 | +**Autorisations requises :** | ||
| 162 | +- `read:user` - Pour l'accès au profil utilisateur (`get_me`) | ||
| 163 | +- `repo` - Pour l'accès aux dépôts (la plupart des outils) | ||
| 164 | +- `read:org` - Pour l'accès aux organisations | ||
| 165 | +- `user` - Informations de base de l'utilisateur | ||
| 166 | + | ||
| 167 | +**Type de token :** Classic Personal Access Token (PAT) | ||
| 168 | + | ||
| 169 | +**Génération du token :** | ||
| 170 | +1. Allez sur [https://github.com/settings/tokens](https://github.com/settings/tokens) | ||
| 171 | +2. Cliquez sur "Generate new token (classic)" | ||
| 172 | +3. Sélectionnez les autorisations requises | ||
| 173 | +4. Générez le token | ||
| 174 | +5. Copiez le token (il ne sera plus affiché) | ||
| 175 | + | ||
| 176 | +--- | ||
| 177 | + | ||
| 178 | +### Context7 MCP Server | ||
| 179 | + | ||
| 180 | +**Point de terminaison :** `https://mcp.context7.com/mcp` | ||
| 181 | + | ||
| 182 | +**Authentification :** Token Bearer (`bearer` type) | ||
| 183 | + | ||
| 184 | +**Token :** Clé API Context7 | ||
| 185 | + | ||
| 186 | +**Génération du token :** | ||
| 187 | +1. Inscrivez-vous sur [https://context7.com](https://context7.com) | ||
| 188 | +2. Obtenez votre clé API depuis le tableau de bord | ||
| 189 | +3. Définissez-la comme variable d'environnement `CONTEXT7_API_KEY` | ||
| 190 | + | ||
| 191 | +--- | ||
| 192 | + | ||
| 193 | +### Wikipedia MCP Server | ||
| 194 | + | ||
| 195 | +**Point de terminaison :** `https://wikipedia-mcp.vercel.app/mcp` | ||
| 196 | + | ||
| 197 | +**Authentification :** Aucune (`none` type) | ||
| 198 | + | ||
| 199 | +**Token :** Non requis | ||
| 200 | + | ||
| 201 | +--- | ||
| 202 | + | ||
| 203 | +### HasData (DuckDuckGo) MCP Server | ||
| 204 | + | ||
| 205 | +**Point de terminaison :** `https://mcp.hasdata.com/mcp?apis=duckduckgo` | ||
| 206 | + | ||
| 207 | +**Authentification :** Clé API (`api_key` type) | ||
| 208 | + | ||
| 209 | +**Token :** Clé API HasData | ||
| 210 | + | ||
| 211 | +**En-têtes :** | ||
| 212 | +- Nom de l'en-tête : `x-api-key` | ||
| 213 | +- Variable d'environnement : `HASDATA_API_KEY` | ||
| 214 | + | ||
| 215 | +--- | ||
| 216 | + | ||
| 217 | +## Résolution des problèmes | ||
| 218 | + | ||
| 219 | +### Erreur "missing bearer token" ou "missing API key" | ||
| 220 | + | ||
| 221 | +**Cause :** La variable d'environnement requise n'est pas définie ou est vide. | ||
| 222 | + | ||
| 223 | +**Solution :** | ||
| 224 | +1. Vérifiez que la variable est définie : `echo $NOM_VARIABLE` | ||
| 225 | +2. Vérifiez qu'elle est exportée : `env | grep NOM_VARIABLE` | ||
| 226 | +3. Vérifiez les fautes de frappe dans le nom de la variable dans votre config | ||
| 227 | + | ||
| 228 | +### L'authentification échoue mais le token fonctionne avec curl | ||
| 229 | + | ||
| 230 | +**Cause :** Exigences d'authentification différentes entre l'API REST et le point de terminaison MCP. | ||
| 231 | + | ||
| 232 | +**Exemple :** API REST GitHub (`api.github.com`) vs MCP GitHub (`api.githubcopilot.com`) | ||
| 233 | + | ||
| 234 | +**Solution :** | ||
| 235 | +- Testez directement avec le point de terminaison MCP : | ||
| 236 | + ```bash | ||
| 237 | + curl -X POST https://api.githubcopilot.com/mcp/ \ | ||
| 238 | + -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \ | ||
| 239 | + -H "Accept: application/json, text/event-stream" \ | ||
| 240 | + -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | ||
| 241 | + ``` | ||
| 242 | +- Assurez-vous que votre token a les autorisations requises pour le serveur MCP | ||
| 243 | + | ||
| 244 | +### Le serveur retourne 403 Forbidden | ||
| 245 | + | ||
| 246 | +**Cause :** Le token manque des permissions requises ou des exigences spécifiques au serveur. | ||
| 247 | + | ||
| 248 | +**Solution :** | ||
| 249 | +- Vérifiez si le token a expiré | ||
| 250 | +- Vérifiez que le token a les bonnes autorisations | ||
| 251 | +- Certains serveurs peuvent nécessiter des en-têtes ou paramètres supplémentaires | ||
| 252 | + | ||
| 253 | +### La connexion au serveur expire | ||
| 254 | + | ||
| 255 | +**Solution :** | ||
| 256 | +- Augmentez le `timeout` dans votre configuration de serveur | ||
| 257 | +- Vérifiez la connectivité réseau vers le point de terminaison du serveur MCP | ||
| 258 | +- Vérifiez que le serveur est en ligne et accessible | ||
| 259 | + | ||
| 260 | +--- | ||
| 261 | + | ||
| 262 | +## Considérations de sécurité | ||
| 263 | + | ||
| 264 | +1. **Portée du token :** N'accordez que les autorisations minimales requises | ||
| 265 | +2. **Stockage du token :** Utilisez des variables d'environnement, pas des fichiers de configuration | ||
| 266 | +3. **Partage du token :** Ne partagez jamais les tokens ni ne les commitez dans le contrôle de version | ||
| 267 | +4. **Rotation du token :** Faites une rotation régulière des tokens, surtout s'ils sont compromis | ||
| 268 | +5. **Sécurité réseau :** Assurez-vous que les connexions aux serveurs MCP utilisent HTTPS | ||
| 269 | + | ||
| 270 | +--- | ||
| 271 | + | ||
| 272 | +## Voir aussi | ||
| 273 | + | ||
| 274 | +- [Référence de configuration MCP](configuration.md) | ||
| 275 | +- [Outils MCP disponibles](outils-disponibles.md) | ||
| 276 | +- [Architecture MCP](explanation/architecture.md) | ||
added
docs/fr/reference/mcp-configuration.md +308 -0 | new file mode 100644 | ||
| @@ -0,0 +1,308 @@ | ||
| 1 | +# Référence de configuration MCP | |
| 2 | + | |
| 3 | +Ce document décrit toutes les options de configuration pour le client MCP (Model Context Protocol) dans mini-me. | |
| 4 | + | |
| 5 | +## Aperçu | |
| 6 | + | |
| 7 | +La configuration MCP fait partie du fichier de configuration principal (généralement `agent.yaml`). Le bloc MCP configure le client et ses serveurs. | |
| 8 | + | |
| 9 | +## Structure de configuration | |
| 10 | + | |
| 11 | +```yaml | |
| 12 | +mcp: | |
| 13 | + enabled: true # Activer/désactiver la prise en charge MCP | |
| 14 | + permissions: ask # Mode de permission par défaut | |
| 15 | + discoveryTimeout: 10s # Délai d'attente pour la découverte des outils | |
| 16 | + servers: # Liste des serveurs MCP | |
| 17 | + - name: nom-du-serveur | |
| 18 | + transport: http # Type de transport (http ou stdio) | |
| 19 | + url: https://example.com/mcp # URL pour le transport HTTP | |
| 20 | + timeout: 30s # Délai d'attente des requêtes | |
| 21 | + auth: # Authentification | |
| 22 | + type: bearer # Type d'auth : none, bearer, api_key | |
| 23 | + tokenEnv: VAR_TOKEN # Variable d'environnement pour le token bearer | |
| 24 | + apiKeyEnv: VAR_CLE_API # Variable d'environnement pour la clé API | |
| 25 | + apiKeyHeader: X-API-Key # Nom de l'en-tête pour la clé API | |
| 26 | + headers: # En-têtes HTTP personnalisés | |
| 27 | + Accept: "application/json, text/event-stream" | |
| 28 | + permissions: ask # Remplacer les permissions globales pour ce serveur | |
| 29 | + tools: # Optionnel : Filtrer les outils à exposer | |
| 30 | + - nom_outil_1 | |
| 31 | + - nom_outil_2 | |
| 32 | +``` | |
| 33 | + | |
| 34 | +## Options de niveau supérieur | |
| 35 | + | |
| 36 | +### `enabled` | |
| 37 | + | |
| 38 | +- **Type :** `boolean` | |
| 39 | +- **Valeur par défaut :** `false` | |
| 40 | +- **Description :** Active ou désactive entièrement la prise en charge du client MCP. | |
| 41 | +- **Exemple :** `enabled: true` | |
| 42 | + | |
| 43 | +### `permissions` | |
| 44 | + | |
| 45 | +- **Type :** `string` | |
| 46 | +- **Valeur par défaut :** `ask` | |
| 47 | +- **Valeurs valides :** `ask`, `auto` | |
| 48 | +- **Description :** Mode de permission par défaut pour les outils MCP. | |
| 49 | + - `ask` : Demande à l'utilisateur avant d'exécuter chaque outil MCP | |
| 50 | + - `auto` : Exécute les outils MCP automatiquement sans demande | |
| 51 | +- **Exemple :** `permissions: ask` | |
| 52 | + | |
| 53 | +### `discoveryTimeout` | |
| 54 | + | |
| 55 | +- **Type :** `duration` | |
| 56 | +- **Valeur par défaut :** `5s` | |
| 57 | +- **Description :** Délai d'attente pour la découverte des outils depuis tous les serveurs MCP au démarrage. Si la découverte prend plus de temps, le serveur sera ignoré avec un avertissement. | |
| 58 | +- **Exemple :** `discoveryTimeout: 10s` | |
| 59 | + | |
| 60 | +## Configuration du serveur | |
| 61 | + | |
| 62 | +Chaque serveur dans la liste `servers` peut avoir les options suivantes : | |
| 63 | + | |
| 64 | +### `name` (Obligatoire) | |
| 65 | + | |
| 66 | +- **Type :** `string` | |
| 67 | +- **Description :** Identifiant unique pour ce serveur MCP. Utilisé comme préfixe pour les noms d'outils (ex: `github_list_repos`). | |
| 68 | +- **Exemple :** `name: github` | |
| 69 | + | |
| 70 | +### `transport` (Obligatoire) | |
| 71 | + | |
| 72 | +- **Type :** `string` | |
| 73 | +- **Valeurs valides :** `http`, `stdio` | |
| 74 | +- **Description :** Protocole de transport pour la connexion au serveur MCP. | |
| 75 | + - `http` : Transport HTTP/HTTPS (pour les serveurs MCP streamables via HTTP) | |
| 76 | + - `stdio` : Transport standard entrée/sortie (N'EST PAS ENCORE IMPLÉMENTÉ dans mini-me) | |
| 77 | +- **Exemple :** `transport: http` | |
| 78 | + | |
| 79 | +### `url` (Obligatoire pour le transport HTTP) | |
| 80 | + | |
| 81 | +- **Type :** `string` | |
| 82 | +- **Description :** Point de terminaison URL du serveur MCP. Pour le transport HTTP, ceci doit être l'URL complète incluant le chemin `/mcp` si requis. | |
| 83 | +- **Exemples :** | |
| 84 | + - `url: https://api.githubcopilot.com/mcp/` | |
| 85 | + - `url: https://wikipedia-mcp.vercel.app/mcp` | |
| 86 | + - `url: https://mcp.context7.com/mcp` | |
| 87 | + | |
| 88 | +### `timeout` | |
| 89 | + | |
| 90 | +- **Type :** `duration` | |
| 91 | +- **Valeur par défaut :** `30s` | |
| 92 | +- **Description :** Délai d'attente pour les requêtes individuelles vers ce serveur MCP. | |
| 93 | +- **Exemple :** `timeout: 60s` | |
| 94 | + | |
| 95 | +## Options d'authentification | |
| 96 | + | |
| 97 | +Le bloc `auth` configure l'authentification pour le serveur MCP. | |
| 98 | + | |
| 99 | +### `type` | |
| 100 | + | |
| 101 | +- **Type :** `string` | |
| 102 | +- **Valeurs valides :** `none`, `bearer`, `api_key` | |
| 103 | +- **Description :** Méthode d'authentification pour le serveur MCP. | |
| 104 | + | |
| 105 | +#### `type: none` | |
| 106 | + | |
| 107 | +Aucune authentification requise. Le serveur accepte les requêtes non authentifiées. | |
| 108 | + | |
| 109 | +**Exemple :** | |
| 110 | +```yaml | |
| 111 | +auth: | |
| 112 | + type: none | |
| 113 | +``` | |
| 114 | + | |
| 115 | +**Cas d'utilisation :** Serveurs MCP publics comme Wikipedia. | |
| 116 | + | |
| 117 | +#### `type: bearer` | |
| 118 | + | |
| 119 | +Authentification par token Bearer (en-tête HTTP Authorization). | |
| 120 | + | |
| 121 | +**Requis :** `tokenEnv` | |
| 122 | + | |
| 123 | +**Exemple :** | |
| 124 | +```yaml | |
| 125 | +auth: | |
| 126 | + type: bearer | |
| 127 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 128 | +``` | |
| 129 | + | |
| 130 | +**Cas d'utilisation :** Serveur MCP GitHub, Context7. | |
| 131 | + | |
| 132 | +#### `type: api_key` | |
| 133 | + | |
| 134 | +Authentification par clé API (en-tête personnalisé). | |
| 135 | + | |
| 136 | +**Requis :** `apiKeyEnv`, `apiKeyHeader` | |
| 137 | + | |
| 138 | +**Exemple :** | |
| 139 | +```yaml | |
| 140 | +auth: | |
| 141 | + type: api_key | |
| 142 | + apiKeyEnv: HASDATA_API_KEY | |
| 143 | + apiKeyHeader: x-api-key | |
| 144 | +``` | |
| 145 | + | |
| 146 | +**Cas d'utilisation :** Serveur MCP DuckDuckGo (via HasData). | |
| 147 | + | |
| 148 | +## Options spécifiques au serveur | |
| 149 | + | |
| 150 | +### `headers` | |
| 151 | + | |
| 152 | +- **Type :** `map[string]string` | |
| 153 | +- **Description :** En-têtes HTTP personnalisés à envoyer avec chaque requête. | |
| 154 | +- **Exemple :** | |
| 155 | +```yaml | |
| 156 | +headers: | |
| 157 | + Accept: "application/json, text/event-stream" | |
| 158 | + Custom-Header: "valeur-personnalisée" | |
| 159 | +``` | |
| 160 | + | |
| 161 | +### `permissions` | |
| 162 | + | |
| 163 | +- **Type :** `string` | |
| 164 | +- **Valeur par défaut :** Hérite de `mcp.permissions` global | |
| 165 | +- **Valeurs valides :** `ask`, `auto` | |
| 166 | +- **Description :** Remplace le paramètre de permission global pour ce serveur spécifique. | |
| 167 | +- **Exemple :** `permissions: auto` | |
| 168 | + | |
| 169 | +### `tools` | |
| 170 | + | |
| 171 | +- **Type :** `list<string>` | |
| 172 | +- **Valeur par défaut :** Tous les outils du serveur | |
| 173 | +- **Description :** Liste optionnelle des outils à exposer depuis ce serveur. Si non spécifié, tous les outils découverts seront disponibles. Si spécifié, seuls les outils listés seront disponibles. | |
| 174 | +- **Exemple :** | |
| 175 | +```yaml | |
| 176 | +tools: | |
| 177 | + - get_me | |
| 178 | + - list_repositories | |
| 179 | + - search_code | |
| 180 | +``` | |
| 181 | + | |
| 182 | +## Exemples de configuration complète | |
| 183 | + | |
| 184 | +### Serveur MCP Wikipedia | |
| 185 | + | |
| 186 | +```yaml | |
| 187 | +mcp: | |
| 188 | + enabled: true | |
| 189 | + servers: | |
| 190 | + - name: wikipedia | |
| 191 | + transport: http | |
| 192 | + url: https://wikipedia-mcp.vercel.app/mcp | |
| 193 | + timeout: 30s | |
| 194 | + auth: | |
| 195 | + type: none | |
| 196 | +``` | |
| 197 | + | |
| 198 | +### Serveur MCP Context7 | |
| 199 | + | |
| 200 | +```yaml | |
| 201 | +mcp: | |
| 202 | + enabled: true | |
| 203 | + servers: | |
| 204 | + - name: context7 | |
| 205 | + transport: http | |
| 206 | + url: https://mcp.context7.com/mcp | |
| 207 | + timeout: 60s | |
| 208 | + auth: | |
| 209 | + type: bearer | |
| 210 | + tokenEnv: CONTEXT7_API_KEY | |
| 211 | + headers: | |
| 212 | + Accept: "application/json, text/event-stream" | |
| 213 | +``` | |
| 214 | + | |
| 215 | +### Serveur MCP GitHub | |
| 216 | + | |
| 217 | +```yaml | |
| 218 | +mcp: | |
| 219 | + enabled: true | |
| 220 | + servers: | |
| 221 | + - name: github | |
| 222 | + transport: http | |
| 223 | + url: https://api.githubcopilot.com/mcp/ | |
| 224 | + timeout: 60s | |
| 225 | + auth: | |
| 226 | + type: bearer | |
| 227 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 228 | + headers: | |
| 229 | + Accept: "application/json, text/event-stream" | |
| 230 | + tools: | |
| 231 | + - get_me | |
| 232 | + - list_repositories | |
| 233 | + - list_issues | |
| 234 | + - get_file_contents | |
| 235 | + - search_code | |
| 236 | +``` | |
| 237 | + | |
| 238 | +### Serveurs multiples | |
| 239 | + | |
| 240 | +```yaml | |
| 241 | +mcp: | |
| 242 | + enabled: true | |
| 243 | + permissions: ask | |
| 244 | + discoveryTimeout: 15s | |
| 245 | + servers: | |
| 246 | + - name: wikipedia | |
| 247 | + transport: http | |
| 248 | + url: https://wikipedia-mcp.vercel.app/mcp | |
| 249 | + auth: | |
| 250 | + type: none | |
| 251 | + | |
| 252 | + - name: context7 | |
| 253 | + transport: http | |
| 254 | + url: https://mcp.context7.com/mcp | |
| 255 | + auth: | |
| 256 | + type: bearer | |
| 257 | + tokenEnv: CONTEXT7_API_KEY | |
| 258 | + | |
| 259 | + - name: github | |
| 260 | + transport: http | |
| 261 | + url: https://api.githubcopilot.com/mcp/ | |
| 262 | + auth: | |
| 263 | + type: bearer | |
| 264 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 265 | + tools: | |
| 266 | + - get_me | |
| 267 | + - list_repositories | |
| 268 | +``` | |
| 269 | + | |
| 270 | +## Variables d'environnement | |
| 271 | + | |
| 272 | +| Variable | Serveur | Description | | |
| 273 | +|----------|---------|-------------| | |
| 274 | +| `GITHUB_PERSONAL_ACCESS_TOKEN` | GitHub | Token d'accès personnel GitHub | | |
| 275 | +| `CONTEXT7_API_KEY` | Context7 | Clé API Context7 | | |
| 276 | +| `HASDATA_API_KEY` | DuckDuckGo/HasData | Clé API HasData | | |
| 277 | + | |
| 278 | +## Validation | |
| 279 | + | |
| 280 | +Les configurations de serveur sont validées au démarrage. Erreurs de validation courantes : | |
| 281 | + | |
| 282 | +- Champs requis manquants (`name`, `transport`, `url`) | |
| 283 | +- Type `transport` invalide | |
| 284 | +- Type `auth.type` invalide | |
| 285 | +- Variable d'environnement manquante pour l'authentification | |
| 286 | +- Durée de délai d'attente invalide | |
| 287 | + | |
| 288 | +Les erreurs seront affichées sous forme d'avertissements au démarrage. | |
| 289 | + | |
| 290 | +## Considérations de performance | |
| 291 | + | |
| 292 | +- Chaque serveur MCP est découverts au démarrage, ce qui ajoute au temps de démarrage | |
| 293 | +- `discoveryTimeout` limite le temps d'attente pour les serveurs lents | |
| 294 | +- Le filtrage des outils avec l'option `tools` réduit le temps de démarrage | |
| 295 | +- Les serveurs dont la découverte échoue seront ignorés avec un avertissement | |
| 296 | + | |
| 297 | +## Remarques de sécurité | |
| 298 | + | |
| 299 | +- **Ne commitez jamais de secrets dans le contrôle de version :** Les variables d'environnement sont le moyen le plus sûr de fournir l'authentification | |
| 300 | +- **Utilisez le principe du moindre privilège :** N'accordez que les autorisations minimales requises pour chaque serveur MCP | |
| 301 | +- **Vérifiez la confiance du serveur :** Ne vous connectez qu'aux serveurs MCP en lesquels vous avez confiance, car ils peuvent exécuter des outils arbitraires | |
| 302 | +- **Mode de permission :** Utilisez `permissions: ask` pour la production afin d'éviter l'exécution inattendue d'outils | |
| 303 | + | |
| 304 | +## Voir aussi | |
| 305 | + | |
| 306 | +- [Premiers pas avec MCP](tutorials/premiers-pas.md) | |
| 307 | +- [Architecture MCP](explanation/architecture.md) | |
| 308 | +- [Décisions de conception](explanation/decisions.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,308 @@ | |||
| 1 | +# Référence de configuration MCP | ||
| 2 | + | ||
| 3 | +Ce document décrit toutes les options de configuration pour le client MCP (Model Context Protocol) dans mini-me. | ||
| 4 | + | ||
| 5 | +## Aperçu | ||
| 6 | + | ||
| 7 | +La configuration MCP fait partie du fichier de configuration principal (généralement `agent.yaml`). Le bloc MCP configure le client et ses serveurs. | ||
| 8 | + | ||
| 9 | +## Structure de configuration | ||
| 10 | + | ||
| 11 | +```yaml | ||
| 12 | +mcp: | ||
| 13 | + enabled: true # Activer/désactiver la prise en charge MCP | ||
| 14 | + permissions: ask # Mode de permission par défaut | ||
| 15 | + discoveryTimeout: 10s # Délai d'attente pour la découverte des outils | ||
| 16 | + servers: # Liste des serveurs MCP | ||
| 17 | + - name: nom-du-serveur | ||
| 18 | + transport: http # Type de transport (http ou stdio) | ||
| 19 | + url: https://example.com/mcp # URL pour le transport HTTP | ||
| 20 | + timeout: 30s # Délai d'attente des requêtes | ||
| 21 | + auth: # Authentification | ||
| 22 | + type: bearer # Type d'auth : none, bearer, api_key | ||
| 23 | + tokenEnv: VAR_TOKEN # Variable d'environnement pour le token bearer | ||
| 24 | + apiKeyEnv: VAR_CLE_API # Variable d'environnement pour la clé API | ||
| 25 | + apiKeyHeader: X-API-Key # Nom de l'en-tête pour la clé API | ||
| 26 | + headers: # En-têtes HTTP personnalisés | ||
| 27 | + Accept: "application/json, text/event-stream" | ||
| 28 | + permissions: ask # Remplacer les permissions globales pour ce serveur | ||
| 29 | + tools: # Optionnel : Filtrer les outils à exposer | ||
| 30 | + - nom_outil_1 | ||
| 31 | + - nom_outil_2 | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +## Options de niveau supérieur | ||
| 35 | + | ||
| 36 | +### `enabled` | ||
| 37 | + | ||
| 38 | +- **Type :** `boolean` | ||
| 39 | +- **Valeur par défaut :** `false` | ||
| 40 | +- **Description :** Active ou désactive entièrement la prise en charge du client MCP. | ||
| 41 | +- **Exemple :** `enabled: true` | ||
| 42 | + | ||
| 43 | +### `permissions` | ||
| 44 | + | ||
| 45 | +- **Type :** `string` | ||
| 46 | +- **Valeur par défaut :** `ask` | ||
| 47 | +- **Valeurs valides :** `ask`, `auto` | ||
| 48 | +- **Description :** Mode de permission par défaut pour les outils MCP. | ||
| 49 | + - `ask` : Demande à l'utilisateur avant d'exécuter chaque outil MCP | ||
| 50 | + - `auto` : Exécute les outils MCP automatiquement sans demande | ||
| 51 | +- **Exemple :** `permissions: ask` | ||
| 52 | + | ||
| 53 | +### `discoveryTimeout` | ||
| 54 | + | ||
| 55 | +- **Type :** `duration` | ||
| 56 | +- **Valeur par défaut :** `5s` | ||
| 57 | +- **Description :** Délai d'attente pour la découverte des outils depuis tous les serveurs MCP au démarrage. Si la découverte prend plus de temps, le serveur sera ignoré avec un avertissement. | ||
| 58 | +- **Exemple :** `discoveryTimeout: 10s` | ||
| 59 | + | ||
| 60 | +## Configuration du serveur | ||
| 61 | + | ||
| 62 | +Chaque serveur dans la liste `servers` peut avoir les options suivantes : | ||
| 63 | + | ||
| 64 | +### `name` (Obligatoire) | ||
| 65 | + | ||
| 66 | +- **Type :** `string` | ||
| 67 | +- **Description :** Identifiant unique pour ce serveur MCP. Utilisé comme préfixe pour les noms d'outils (ex: `github_list_repos`). | ||
| 68 | +- **Exemple :** `name: github` | ||
| 69 | + | ||
| 70 | +### `transport` (Obligatoire) | ||
| 71 | + | ||
| 72 | +- **Type :** `string` | ||
| 73 | +- **Valeurs valides :** `http`, `stdio` | ||
| 74 | +- **Description :** Protocole de transport pour la connexion au serveur MCP. | ||
| 75 | + - `http` : Transport HTTP/HTTPS (pour les serveurs MCP streamables via HTTP) | ||
| 76 | + - `stdio` : Transport standard entrée/sortie (N'EST PAS ENCORE IMPLÉMENTÉ dans mini-me) | ||
| 77 | +- **Exemple :** `transport: http` | ||
| 78 | + | ||
| 79 | +### `url` (Obligatoire pour le transport HTTP) | ||
| 80 | + | ||
| 81 | +- **Type :** `string` | ||
| 82 | +- **Description :** Point de terminaison URL du serveur MCP. Pour le transport HTTP, ceci doit être l'URL complète incluant le chemin `/mcp` si requis. | ||
| 83 | +- **Exemples :** | ||
| 84 | + - `url: https://api.githubcopilot.com/mcp/` | ||
| 85 | + - `url: https://wikipedia-mcp.vercel.app/mcp` | ||
| 86 | + - `url: https://mcp.context7.com/mcp` | ||
| 87 | + | ||
| 88 | +### `timeout` | ||
| 89 | + | ||
| 90 | +- **Type :** `duration` | ||
| 91 | +- **Valeur par défaut :** `30s` | ||
| 92 | +- **Description :** Délai d'attente pour les requêtes individuelles vers ce serveur MCP. | ||
| 93 | +- **Exemple :** `timeout: 60s` | ||
| 94 | + | ||
| 95 | +## Options d'authentification | ||
| 96 | + | ||
| 97 | +Le bloc `auth` configure l'authentification pour le serveur MCP. | ||
| 98 | + | ||
| 99 | +### `type` | ||
| 100 | + | ||
| 101 | +- **Type :** `string` | ||
| 102 | +- **Valeurs valides :** `none`, `bearer`, `api_key` | ||
| 103 | +- **Description :** Méthode d'authentification pour le serveur MCP. | ||
| 104 | + | ||
| 105 | +#### `type: none` | ||
| 106 | + | ||
| 107 | +Aucune authentification requise. Le serveur accepte les requêtes non authentifiées. | ||
| 108 | + | ||
| 109 | +**Exemple :** | ||
| 110 | +```yaml | ||
| 111 | +auth: | ||
| 112 | + type: none | ||
| 113 | +``` | ||
| 114 | + | ||
| 115 | +**Cas d'utilisation :** Serveurs MCP publics comme Wikipedia. | ||
| 116 | + | ||
| 117 | +#### `type: bearer` | ||
| 118 | + | ||
| 119 | +Authentification par token Bearer (en-tête HTTP Authorization). | ||
| 120 | + | ||
| 121 | +**Requis :** `tokenEnv` | ||
| 122 | + | ||
| 123 | +**Exemple :** | ||
| 124 | +```yaml | ||
| 125 | +auth: | ||
| 126 | + type: bearer | ||
| 127 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 128 | +``` | ||
| 129 | + | ||
| 130 | +**Cas d'utilisation :** Serveur MCP GitHub, Context7. | ||
| 131 | + | ||
| 132 | +#### `type: api_key` | ||
| 133 | + | ||
| 134 | +Authentification par clé API (en-tête personnalisé). | ||
| 135 | + | ||
| 136 | +**Requis :** `apiKeyEnv`, `apiKeyHeader` | ||
| 137 | + | ||
| 138 | +**Exemple :** | ||
| 139 | +```yaml | ||
| 140 | +auth: | ||
| 141 | + type: api_key | ||
| 142 | + apiKeyEnv: HASDATA_API_KEY | ||
| 143 | + apiKeyHeader: x-api-key | ||
| 144 | +``` | ||
| 145 | + | ||
| 146 | +**Cas d'utilisation :** Serveur MCP DuckDuckGo (via HasData). | ||
| 147 | + | ||
| 148 | +## Options spécifiques au serveur | ||
| 149 | + | ||
| 150 | +### `headers` | ||
| 151 | + | ||
| 152 | +- **Type :** `map[string]string` | ||
| 153 | +- **Description :** En-têtes HTTP personnalisés à envoyer avec chaque requête. | ||
| 154 | +- **Exemple :** | ||
| 155 | +```yaml | ||
| 156 | +headers: | ||
| 157 | + Accept: "application/json, text/event-stream" | ||
| 158 | + Custom-Header: "valeur-personnalisée" | ||
| 159 | +``` | ||
| 160 | + | ||
| 161 | +### `permissions` | ||
| 162 | + | ||
| 163 | +- **Type :** `string` | ||
| 164 | +- **Valeur par défaut :** Hérite de `mcp.permissions` global | ||
| 165 | +- **Valeurs valides :** `ask`, `auto` | ||
| 166 | +- **Description :** Remplace le paramètre de permission global pour ce serveur spécifique. | ||
| 167 | +- **Exemple :** `permissions: auto` | ||
| 168 | + | ||
| 169 | +### `tools` | ||
| 170 | + | ||
| 171 | +- **Type :** `list<string>` | ||
| 172 | +- **Valeur par défaut :** Tous les outils du serveur | ||
| 173 | +- **Description :** Liste optionnelle des outils à exposer depuis ce serveur. Si non spécifié, tous les outils découverts seront disponibles. Si spécifié, seuls les outils listés seront disponibles. | ||
| 174 | +- **Exemple :** | ||
| 175 | +```yaml | ||
| 176 | +tools: | ||
| 177 | + - get_me | ||
| 178 | + - list_repositories | ||
| 179 | + - search_code | ||
| 180 | +``` | ||
| 181 | + | ||
| 182 | +## Exemples de configuration complète | ||
| 183 | + | ||
| 184 | +### Serveur MCP Wikipedia | ||
| 185 | + | ||
| 186 | +```yaml | ||
| 187 | +mcp: | ||
| 188 | + enabled: true | ||
| 189 | + servers: | ||
| 190 | + - name: wikipedia | ||
| 191 | + transport: http | ||
| 192 | + url: https://wikipedia-mcp.vercel.app/mcp | ||
| 193 | + timeout: 30s | ||
| 194 | + auth: | ||
| 195 | + type: none | ||
| 196 | +``` | ||
| 197 | + | ||
| 198 | +### Serveur MCP Context7 | ||
| 199 | + | ||
| 200 | +```yaml | ||
| 201 | +mcp: | ||
| 202 | + enabled: true | ||
| 203 | + servers: | ||
| 204 | + - name: context7 | ||
| 205 | + transport: http | ||
| 206 | + url: https://mcp.context7.com/mcp | ||
| 207 | + timeout: 60s | ||
| 208 | + auth: | ||
| 209 | + type: bearer | ||
| 210 | + tokenEnv: CONTEXT7_API_KEY | ||
| 211 | + headers: | ||
| 212 | + Accept: "application/json, text/event-stream" | ||
| 213 | +``` | ||
| 214 | + | ||
| 215 | +### Serveur MCP GitHub | ||
| 216 | + | ||
| 217 | +```yaml | ||
| 218 | +mcp: | ||
| 219 | + enabled: true | ||
| 220 | + servers: | ||
| 221 | + - name: github | ||
| 222 | + transport: http | ||
| 223 | + url: https://api.githubcopilot.com/mcp/ | ||
| 224 | + timeout: 60s | ||
| 225 | + auth: | ||
| 226 | + type: bearer | ||
| 227 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 228 | + headers: | ||
| 229 | + Accept: "application/json, text/event-stream" | ||
| 230 | + tools: | ||
| 231 | + - get_me | ||
| 232 | + - list_repositories | ||
| 233 | + - list_issues | ||
| 234 | + - get_file_contents | ||
| 235 | + - search_code | ||
| 236 | +``` | ||
| 237 | + | ||
| 238 | +### Serveurs multiples | ||
| 239 | + | ||
| 240 | +```yaml | ||
| 241 | +mcp: | ||
| 242 | + enabled: true | ||
| 243 | + permissions: ask | ||
| 244 | + discoveryTimeout: 15s | ||
| 245 | + servers: | ||
| 246 | + - name: wikipedia | ||
| 247 | + transport: http | ||
| 248 | + url: https://wikipedia-mcp.vercel.app/mcp | ||
| 249 | + auth: | ||
| 250 | + type: none | ||
| 251 | + | ||
| 252 | + - name: context7 | ||
| 253 | + transport: http | ||
| 254 | + url: https://mcp.context7.com/mcp | ||
| 255 | + auth: | ||
| 256 | + type: bearer | ||
| 257 | + tokenEnv: CONTEXT7_API_KEY | ||
| 258 | + | ||
| 259 | + - name: github | ||
| 260 | + transport: http | ||
| 261 | + url: https://api.githubcopilot.com/mcp/ | ||
| 262 | + auth: | ||
| 263 | + type: bearer | ||
| 264 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 265 | + tools: | ||
| 266 | + - get_me | ||
| 267 | + - list_repositories | ||
| 268 | +``` | ||
| 269 | + | ||
| 270 | +## Variables d'environnement | ||
| 271 | + | ||
| 272 | +| Variable | Serveur | Description | | ||
| 273 | +|----------|---------|-------------| | ||
| 274 | +| `GITHUB_PERSONAL_ACCESS_TOKEN` | GitHub | Token d'accès personnel GitHub | | ||
| 275 | +| `CONTEXT7_API_KEY` | Context7 | Clé API Context7 | | ||
| 276 | +| `HASDATA_API_KEY` | DuckDuckGo/HasData | Clé API HasData | | ||
| 277 | + | ||
| 278 | +## Validation | ||
| 279 | + | ||
| 280 | +Les configurations de serveur sont validées au démarrage. Erreurs de validation courantes : | ||
| 281 | + | ||
| 282 | +- Champs requis manquants (`name`, `transport`, `url`) | ||
| 283 | +- Type `transport` invalide | ||
| 284 | +- Type `auth.type` invalide | ||
| 285 | +- Variable d'environnement manquante pour l'authentification | ||
| 286 | +- Durée de délai d'attente invalide | ||
| 287 | + | ||
| 288 | +Les erreurs seront affichées sous forme d'avertissements au démarrage. | ||
| 289 | + | ||
| 290 | +## Considérations de performance | ||
| 291 | + | ||
| 292 | +- Chaque serveur MCP est découverts au démarrage, ce qui ajoute au temps de démarrage | ||
| 293 | +- `discoveryTimeout` limite le temps d'attente pour les serveurs lents | ||
| 294 | +- Le filtrage des outils avec l'option `tools` réduit le temps de démarrage | ||
| 295 | +- Les serveurs dont la découverte échoue seront ignorés avec un avertissement | ||
| 296 | + | ||
| 297 | +## Remarques de sécurité | ||
| 298 | + | ||
| 299 | +- **Ne commitez jamais de secrets dans le contrôle de version :** Les variables d'environnement sont le moyen le plus sûr de fournir l'authentification | ||
| 300 | +- **Utilisez le principe du moindre privilège :** N'accordez que les autorisations minimales requises pour chaque serveur MCP | ||
| 301 | +- **Vérifiez la confiance du serveur :** Ne vous connectez qu'aux serveurs MCP en lesquels vous avez confiance, car ils peuvent exécuter des outils arbitraires | ||
| 302 | +- **Mode de permission :** Utilisez `permissions: ask` pour la production afin d'éviter l'exécution inattendue d'outils | ||
| 303 | + | ||
| 304 | +## Voir aussi | ||
| 305 | + | ||
| 306 | +- [Premiers pas avec MCP](tutorials/premiers-pas.md) | ||
| 307 | +- [Architecture MCP](explanation/architecture.md) | ||
| 308 | +- [Décisions de conception](explanation/decisions.md) | ||
added
docs/fr/reference/mcp-outils-disponibles.md +223 -0 | new file mode 100644 | ||
| @@ -0,0 +1,223 @@ | ||
| 1 | +# Référence des outils MCP disponibles | |
| 2 | + | |
| 3 | +Ce document liste les outils disponibles depuis divers serveurs MCP qui peuvent être connectés à mini-me. | |
| 4 | + | |
| 5 | +## Aperçu | |
| 6 | + | |
| 7 | +Chaque serveur MCP fournit son propre ensemble d'outils. Une fois connectés, ces outils sont préfixés avec le nom du serveur pour éviter les collisions. Par exemple, l'outil `list_repositories` du serveur GitHub devient `github_list_repositories`. | |
| 8 | + | |
| 9 | +## Découverte des outils | |
| 10 | + | |
| 11 | +Les outils sont découverts au démarrage de mini-me. Le processus de découverte : | |
| 12 | + | |
| 13 | +1. Se connecte à chaque serveur MCP configuré | |
| 14 | +2. Demande la liste des outils disponibles via le protocole MCP | |
| 15 | +3. Filtre les outils qui utilisent des entrées/sorties binaires (mini-me ne prend en charge que le texte) | |
| 16 | +4. Filtre optionnellement selon la liste `tools` dans la configuration du serveur | |
| 17 | +5. Nettoie les noms des outils pour s'assurer qu'ils sont des identifiants valides | |
| 18 | + | |
| 19 | +## Outils par serveur | |
| 20 | + | |
| 21 | +### Serveur MCP Wikipedia | |
| 22 | + | |
| 23 | +**Nom du serveur :** `wikipedia` | |
| 24 | + | |
| 25 | +| Nom de l'outil | Nom préfixé | Description | | |
| 26 | +|----------------|-------------|-------------| | |
| 27 | +| `search_wikipedia` | `wikipedia_search_wikipedia` | Rechercher des articles Wikipedia | | |
| 28 | +| `get_article` | `wikipedia_get_article` | Obtenir le contenu complet d'un article | | |
| 29 | +| `get_summary` | `wikipedia_get_summary` | Obtenir un résumé d'article | | |
| 30 | +| `get_sections` | `wikipedia_get_sections` | Obtenir les sections/plan d'un article | | |
| 31 | +| `get_links` | `wikipedia_get_links` | Obtenir les liens d'un article | | |
| 32 | +| `get_coordinates` | `wikipedia_get_coordinates` | Obtenir les coordonnées géographiques | | |
| 33 | +| `get_related_topics` | `wikipedia_get_related_topics` | Obtenir les sujets liés | | |
| 34 | +| `summarize_article_for_query` | `wikipedia_summarize_article_for_query` | Résumer les sections pertinentes | | |
| 35 | +| `summarize_article_section` | `wikipedia_summarize_article_section` | Résumer une section spécifique | | |
| 36 | +| `extract_key_facts` | `wikipedia_extract_key_facts` | Extraire les faits clés | | |
| 37 | +| `test_wikipedia_connectivity` | `wikipedia_test_wikipedia_connectivity` | Tester la connexion | | |
| 38 | +| `list_supported_countries` | `wikipedia_list_supported_countries` | Lister les langues prises en charge | | |
| 39 | +| `multi_search_wikipedia` | `wikipedia_multi_search_wikipedia` | Recherche multi-langues | | |
| 40 | + | |
| 41 | +### Serveur MCP Context7 | |
| 42 | + | |
| 43 | +**Nom du serveur :** `context7` | |
| 44 | + | |
| 45 | +| Nom de l'outil | Nom préfixé | Description | | |
| 46 | +|----------------|-------------|-------------| | |
| 47 | +| `resolve-library-id` | `context7_resolve_library_id` | Résoudre un nom de bibliothèque en ID | | |
| 48 | +| `query-docs` | `context7_query_docs` | Interroger la documentation pour une bibliothèque | | |
| 49 | + | |
| 50 | +**Conseils d'utilisation :** | |
| 51 | +- Appelez d'abord `resolve-library-id` pour obtenir un ID de bibliothèque | |
| 52 | +- Utilisez l'ID de bibliothèque avec `query-docs` pour rechercher dans la documentation | |
| 53 | + | |
| 54 | +**Exemple de workflow :** | |
| 55 | +``` | |
| 56 | +1. context7_resolve_library_id(query="React hooks", libraryName="react") | |
| 57 | +2. context7_query_docs(libraryId="/facebook/react", query="exemples useEffect") | |
| 58 | +``` | |
| 59 | + | |
| 60 | +### Serveur MCP GitHub | |
| 61 | + | |
| 62 | +**Nom du serveur :** `github` | |
| 63 | + | |
| 64 | +#### Outils de dépôt | |
| 65 | +| Nom de l'outil | Nom préfixé | Description | | |
| 66 | +|----------------|-------------|-------------| | |
| 67 | +| `get_me` | `github_get_me` | Obtenir le profil utilisateur authentifié | | |
| 68 | +| `list_repositories` | `github_list_repositories` | Lister les dépôts de l'utilisateur | | |
| 69 | +| `list_repository_collaborators` | `github_list_repository_collaborators` | Lister les collaborateurs d'un dépôt | | |
| 70 | +| `create_repository` | `github_create_repository` | Créer un nouveau dépôt | | |
| 71 | +| `fork_repository` | `github_fork_repository` | Forker un dépôt | | |
| 72 | + | |
| 73 | +#### Outils de code | |
| 74 | +| Nom de l'outil | Nom préfixé | Description | | |
| 75 | +|----------------|-------------|-------------| | |
| 76 | +| `get_file_contents` | `github_get_file_contents` | Obtenir le contenu d'un fichier | | |
| 77 | +| `create_or_update_file` | `github_create_or_update_file` | Créer ou mettre à jour un fichier | | |
| 78 | +| `delete_file` | `github_delete_file` | Supprimer un fichier | | |
| 79 | +| `list_branches` | `github_list_branches` | Lister les branches d'un dépôt | | |
| 80 | +| `list_commits` | `github_list_commits` | Lister les commits | | |
| 81 | +| `get_commit` | `github_get_commit` | Obtenir les détails d'un commit | | |
| 82 | +| `create_branch` | `github_create_branch` | Créer une nouvelle branche | | |
| 83 | +| `update_pull_request_branch` | `github_update_pull_request_branch` | Mettre à jour la branche d'une PR | | |
| 84 | +| `push_files` | `github_push_files` | Pousser plusieurs fichiers | | |
| 85 | + | |
| 86 | +#### Outils d'issues et de Pull Requests | |
| 87 | +| Nom de l'outil | Nom préfixé | Description | | |
| 88 | +|----------------|-------------|-------------| | |
| 89 | +| `list_issues` | `github_list_issues` | Lister les issues d'un dépôt | | |
| 90 | +| `issue_read` | `github_issue_read` | Lire les détails d'une issue | | |
| 91 | +| `issue_write` | `github_issue_write` | Créer/mettre à jour une issue | | |
| 92 | +| `list_pull_requests` | `github_list_pull_requests` | Lister les PRs | | |
| 93 | +| `pull_request_read` | `github_pull_request_read` | Lire les détails d'une PR | | |
| 94 | +| `pull_request_write` | `github_pull_request_write` | Écrire une revue de PR | | |
| 95 | +| `pull_request_review_write` | `github_pull_request_review_write` | Créer/soumettre une revue de PR | | |
| 96 | +| `create_pull_request` | `github_create_pull_request` | Créer une nouvelle PR | | |
| 97 | +| `merge_pull_request` | `github_merge_pull_request` | Fusionner une PR | | |
| 98 | +| `update_pull_request` | `github_update_pull_request` | Mettre à jour une PR | | |
| 99 | +| `create_or_update_issue` | `github_issue_write` | Créer ou mettre à jour une issue | | |
| 100 | +| `add_issue_comment` | `github_add_issue_comment` | Ajouter un commentaire à une issue | | |
| 101 | +| `update_issue_comment` | `github_update_issue_comment` | Mettre à jour un commentaire d'issue | | |
| 102 | +| `add_comment_to_pending_review` | `github_add_comment_to_pending_review` | Ajouter un commentaire de revue | | |
| 103 | +| `add_reply_to_pull_request_comment` | `github_add_reply_to_pull_request_comment` | Répondre à un commentaire de PR | | |
| 104 | +| `add_pull_request_comment` | `github_add_pull_request_comment` | Ajouter un commentaire de PR | | |
| 105 | +| `get_copilot_job_status` | `github_get_copilot_job_status` | Obtenir l'état d'un job Copilot | | |
| 106 | +| `request_copilot_review` | `github_request_copilot_review` | Demander une revue Copilot | | |
| 107 | + | |
| 108 | +#### Outils de recherche | |
| 109 | +| Nom de l'outil | Nom préfixé | Description | | |
| 110 | +|----------------|-------------|-------------| | |
| 111 | +| `search_code` | `github_search_code` | Rechercher du code | | |
| 112 | +| `search_issues` | `github_search_issues` | Rechercher des issues | | |
| 113 | +| `search_pull_requests` | `github_search_pull_requests` | Rechercher des PRs | | |
| 114 | +| `search_repositories` | `github_search_repositories` | Rechercher des dépôts | | |
| 115 | +| `search_commits` | `github_search_commits` | Rechercher des commits | | |
| 116 | +| `search_users` | `github_search_users` | Rechercher des utilisateurs | | |
| 117 | + | |
| 118 | +#### Outils de labels et de jalons | |
| 119 | +| Nom de l'outil | Nom préfixé | Description | | |
| 120 | +|----------------|-------------|-------------| | |
| 121 | +| `get_label` | `github_get_label` | Obtenir un label spécifique | | |
| 122 | +| `get_team_members` | `github_get_team_members` | Obtenir les membres d'une équipe | | |
| 123 | +| `get_teams` | `github_get_teams` | Obtenir les équipes de l'utilisateur | | |
| 124 | +| `list_issue_fields` | `github_list_issue_fields` | Lister les champs d'issue | | |
| 125 | +| `list_issue_types` | `github_list_issue_types` | Lister les types d'issue | | |
| 126 | +| `list_releases` | `github_list_releases` | Lister les versions | | |
| 127 | +| `list_tags` | `github_list_tags` | Lister les tags git | | |
| 128 | +| `get_release_by_tag` | `github_get_release_by_tag` | Obtenir une version par tag | | |
| 129 | +| `get_latest_release` | `github_get_latest_release` | Obtenir la dernière version | | |
| 130 | +| `get_tag` | `github_get_tag` | Obtenir les détails d'un tag | | |
| 131 | + | |
| 132 | +#### Outils Copilot | |
| 133 | +| Nom de l'outil | Nom préfixé | Description | | |
| 134 | +|----------------|-------------|-------------| | |
| 135 | +| `create_pull_request_with_copilot` | `github_create_pull_request_with_copilot` | Créer une PR avec Copilot | | |
| 136 | +| `assign_copilot_to_issue` | `github_assign_copilot_to_issue` | Assigner Copilot à une issue | | |
| 137 | + | |
| 138 | +#### Outils de sous-issues | |
| 139 | +| Nom de l'outil | Nom préfixé | Description | | |
| 140 | +|----------------|-------------|-------------| | |
| 141 | +| `sub_issue_write` | `github_sub_issue_write` | Ajouter/supprimer/reprioritiser des sous-issues | | |
| 142 | + | |
| 143 | +#### Analyse de secrets | |
| 144 | +| Nom de l'outil | Nom préfixé | Description | | |
| 145 | +|----------------|-------------|-------------| | |
| 146 | +| `run_secret_scanning` | `github_run_secret_scanning` | Exécuter l'analyse de secrets | | |
| 147 | + | |
| 148 | +## Préfixage des noms d'outils | |
| 149 | + | |
| 150 | +Tous les outils MCP sont préfixés avec le nom de leur serveur pour éviter les collisions de noms. Le format est : | |
| 151 | + | |
| 152 | +``` | |
| 153 | +{nom_serveur}_{nom_original_outil} | |
| 154 | +``` | |
| 155 | + | |
| 156 | +Par exemple : | |
| 157 | +- Serveur : `wikipedia`, Outil : `search_wikipedia` → Préfixé : `wikipedia_search_wikipedia` | |
| 158 | +- Serveur : `github`, Outil : `list_repositories` → Préfixé : `github_list_repositories` | |
| 159 | +- Serveur : `context7`, Outil : `resolve-library-id` → Préfixé : `context7_resolve_library_id` (remarque : les tirets deviennent des underscores) | |
| 160 | + | |
| 161 | +## Nettoyage des noms d'outils | |
| 162 | + | |
| 163 | +Les noms d'outils sont nettoyés pour être des identifiants Go valides : | |
| 164 | + | |
| 165 | +1. Les caractères non alphanumériques sont remplacés par des underscores (`_`) | |
| 166 | +2. Les underscores de début/fin sont supprimés | |
| 167 | +3. Les underscores consécutifs multiples sont réduits à un seul | |
| 168 | +4. Si le résultat est vide, il devient `"tool"` | |
| 169 | + | |
| 170 | +Exemples : | |
| 171 | +- `resolve-library-id` → `resolve_library_id` | |
| 172 | +- `query-docs` → `query_docs` | |
| 173 | +- `add-comment_to-pending-review` → `add_comment_to_pending_review` | |
| 174 | + | |
| 175 | +## Paramètres requis | |
| 176 | + | |
| 177 | +Chaque outil a son propre schéma d'entrée définissant les paramètres requis et optionnels. Les paramètres requis doivent être fournis lors de l'appel de l'outil. | |
| 178 | + | |
| 179 | +Pour voir les paramètres requis pour un outil spécifique, utilisez : | |
| 180 | +``` | |
| 181 | +Utilisez l'outil {nom_outil} | |
| 182 | +``` | |
| 183 | + | |
| 184 | +Mini-me vous affichera la description de l'outil incluant les paramètres requis. | |
| 185 | + | |
| 186 | +## Filtrage des outils | |
| 187 | + | |
| 188 | +Vous pouvez filtrer les outils d'un serveur qui sont exposés en spécifiant une liste `tools` dans la configuration du serveur : | |
| 189 | + | |
| 190 | +```yaml | |
| 191 | +servers: | |
| 192 | + - name: github | |
| 193 | + transport: http | |
| 194 | + url: https://api.githubcopilot.com/mcp/ | |
| 195 | + auth: | |
| 196 | + type: bearer | |
| 197 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | |
| 198 | + tools: # Seuls ces outils seront disponibles | |
| 199 | + - get_me | |
| 200 | + - list_repositories | |
| 201 | + - search_code | |
| 202 | +``` | |
| 203 | + | |
| 204 | +## Filtrage des outils binaires | |
| 205 | + | |
| 206 | +Mini-me **ne prend en charge que les entrées/sorties texte** pour les outils MCP. Tout outil qui nécessite des données binaires (images, audio, etc.) est automatiquement filtré et un avertissement est affiché au démarrage. | |
| 207 | + | |
| 208 | +Cci est une mesure de sécurité pour garantir la compatibilité avec l'architecture texte uniquement de mini-me. | |
| 209 | + | |
| 210 | +## Exécution des outils | |
| 211 | + | |
| 212 | +Lorsque vous utilisez un outil MCP : | |
| 213 | + | |
| 214 | +1. **Vérification des permissions :** Si les permissions sont définies sur `ask`, il vous sera demandé de confirmer | |
| 215 | +2. **Validation des entrées :** Les paramètres requis sont validés | |
| 216 | +3. **Délai d'attente :** Le délai d'attente configuré du serveur s'applique | |
| 217 | +4. **Sortie :** Les résultats sont retournés sous forme de texte (le contenu non texte est ignoré) | |
| 218 | +5. **Troncature :** La sortie peut être tronquée si elle dépasse `maxOutput` caractères | |
| 219 | + | |
| 220 | +## Voir aussi | |
| 221 | + | |
| 222 | +- [Configuration MCP](configuration.md) | |
| 223 | +- [Architecture MCP](explanation/architecture.md) | |
| new file mode 100644 | |||
| @@ -0,0 +1,223 @@ | |||
| 1 | +# Référence des outils MCP disponibles | ||
| 2 | + | ||
| 3 | +Ce document liste les outils disponibles depuis divers serveurs MCP qui peuvent être connectés à mini-me. | ||
| 4 | + | ||
| 5 | +## Aperçu | ||
| 6 | + | ||
| 7 | +Chaque serveur MCP fournit son propre ensemble d'outils. Une fois connectés, ces outils sont préfixés avec le nom du serveur pour éviter les collisions. Par exemple, l'outil `list_repositories` du serveur GitHub devient `github_list_repositories`. | ||
| 8 | + | ||
| 9 | +## Découverte des outils | ||
| 10 | + | ||
| 11 | +Les outils sont découverts au démarrage de mini-me. Le processus de découverte : | ||
| 12 | + | ||
| 13 | +1. Se connecte à chaque serveur MCP configuré | ||
| 14 | +2. Demande la liste des outils disponibles via le protocole MCP | ||
| 15 | +3. Filtre les outils qui utilisent des entrées/sorties binaires (mini-me ne prend en charge que le texte) | ||
| 16 | +4. Filtre optionnellement selon la liste `tools` dans la configuration du serveur | ||
| 17 | +5. Nettoie les noms des outils pour s'assurer qu'ils sont des identifiants valides | ||
| 18 | + | ||
| 19 | +## Outils par serveur | ||
| 20 | + | ||
| 21 | +### Serveur MCP Wikipedia | ||
| 22 | + | ||
| 23 | +**Nom du serveur :** `wikipedia` | ||
| 24 | + | ||
| 25 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 26 | +|----------------|-------------|-------------| | ||
| 27 | +| `search_wikipedia` | `wikipedia_search_wikipedia` | Rechercher des articles Wikipedia | | ||
| 28 | +| `get_article` | `wikipedia_get_article` | Obtenir le contenu complet d'un article | | ||
| 29 | +| `get_summary` | `wikipedia_get_summary` | Obtenir un résumé d'article | | ||
| 30 | +| `get_sections` | `wikipedia_get_sections` | Obtenir les sections/plan d'un article | | ||
| 31 | +| `get_links` | `wikipedia_get_links` | Obtenir les liens d'un article | | ||
| 32 | +| `get_coordinates` | `wikipedia_get_coordinates` | Obtenir les coordonnées géographiques | | ||
| 33 | +| `get_related_topics` | `wikipedia_get_related_topics` | Obtenir les sujets liés | | ||
| 34 | +| `summarize_article_for_query` | `wikipedia_summarize_article_for_query` | Résumer les sections pertinentes | | ||
| 35 | +| `summarize_article_section` | `wikipedia_summarize_article_section` | Résumer une section spécifique | | ||
| 36 | +| `extract_key_facts` | `wikipedia_extract_key_facts` | Extraire les faits clés | | ||
| 37 | +| `test_wikipedia_connectivity` | `wikipedia_test_wikipedia_connectivity` | Tester la connexion | | ||
| 38 | +| `list_supported_countries` | `wikipedia_list_supported_countries` | Lister les langues prises en charge | | ||
| 39 | +| `multi_search_wikipedia` | `wikipedia_multi_search_wikipedia` | Recherche multi-langues | | ||
| 40 | + | ||
| 41 | +### Serveur MCP Context7 | ||
| 42 | + | ||
| 43 | +**Nom du serveur :** `context7` | ||
| 44 | + | ||
| 45 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 46 | +|----------------|-------------|-------------| | ||
| 47 | +| `resolve-library-id` | `context7_resolve_library_id` | Résoudre un nom de bibliothèque en ID | | ||
| 48 | +| `query-docs` | `context7_query_docs` | Interroger la documentation pour une bibliothèque | | ||
| 49 | + | ||
| 50 | +**Conseils d'utilisation :** | ||
| 51 | +- Appelez d'abord `resolve-library-id` pour obtenir un ID de bibliothèque | ||
| 52 | +- Utilisez l'ID de bibliothèque avec `query-docs` pour rechercher dans la documentation | ||
| 53 | + | ||
| 54 | +**Exemple de workflow :** | ||
| 55 | +``` | ||
| 56 | +1. context7_resolve_library_id(query="React hooks", libraryName="react") | ||
| 57 | +2. context7_query_docs(libraryId="/facebook/react", query="exemples useEffect") | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +### Serveur MCP GitHub | ||
| 61 | + | ||
| 62 | +**Nom du serveur :** `github` | ||
| 63 | + | ||
| 64 | +#### Outils de dépôt | ||
| 65 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 66 | +|----------------|-------------|-------------| | ||
| 67 | +| `get_me` | `github_get_me` | Obtenir le profil utilisateur authentifié | | ||
| 68 | +| `list_repositories` | `github_list_repositories` | Lister les dépôts de l'utilisateur | | ||
| 69 | +| `list_repository_collaborators` | `github_list_repository_collaborators` | Lister les collaborateurs d'un dépôt | | ||
| 70 | +| `create_repository` | `github_create_repository` | Créer un nouveau dépôt | | ||
| 71 | +| `fork_repository` | `github_fork_repository` | Forker un dépôt | | ||
| 72 | + | ||
| 73 | +#### Outils de code | ||
| 74 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 75 | +|----------------|-------------|-------------| | ||
| 76 | +| `get_file_contents` | `github_get_file_contents` | Obtenir le contenu d'un fichier | | ||
| 77 | +| `create_or_update_file` | `github_create_or_update_file` | Créer ou mettre à jour un fichier | | ||
| 78 | +| `delete_file` | `github_delete_file` | Supprimer un fichier | | ||
| 79 | +| `list_branches` | `github_list_branches` | Lister les branches d'un dépôt | | ||
| 80 | +| `list_commits` | `github_list_commits` | Lister les commits | | ||
| 81 | +| `get_commit` | `github_get_commit` | Obtenir les détails d'un commit | | ||
| 82 | +| `create_branch` | `github_create_branch` | Créer une nouvelle branche | | ||
| 83 | +| `update_pull_request_branch` | `github_update_pull_request_branch` | Mettre à jour la branche d'une PR | | ||
| 84 | +| `push_files` | `github_push_files` | Pousser plusieurs fichiers | | ||
| 85 | + | ||
| 86 | +#### Outils d'issues et de Pull Requests | ||
| 87 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 88 | +|----------------|-------------|-------------| | ||
| 89 | +| `list_issues` | `github_list_issues` | Lister les issues d'un dépôt | | ||
| 90 | +| `issue_read` | `github_issue_read` | Lire les détails d'une issue | | ||
| 91 | +| `issue_write` | `github_issue_write` | Créer/mettre à jour une issue | | ||
| 92 | +| `list_pull_requests` | `github_list_pull_requests` | Lister les PRs | | ||
| 93 | +| `pull_request_read` | `github_pull_request_read` | Lire les détails d'une PR | | ||
| 94 | +| `pull_request_write` | `github_pull_request_write` | Écrire une revue de PR | | ||
| 95 | +| `pull_request_review_write` | `github_pull_request_review_write` | Créer/soumettre une revue de PR | | ||
| 96 | +| `create_pull_request` | `github_create_pull_request` | Créer une nouvelle PR | | ||
| 97 | +| `merge_pull_request` | `github_merge_pull_request` | Fusionner une PR | | ||
| 98 | +| `update_pull_request` | `github_update_pull_request` | Mettre à jour une PR | | ||
| 99 | +| `create_or_update_issue` | `github_issue_write` | Créer ou mettre à jour une issue | | ||
| 100 | +| `add_issue_comment` | `github_add_issue_comment` | Ajouter un commentaire à une issue | | ||
| 101 | +| `update_issue_comment` | `github_update_issue_comment` | Mettre à jour un commentaire d'issue | | ||
| 102 | +| `add_comment_to_pending_review` | `github_add_comment_to_pending_review` | Ajouter un commentaire de revue | | ||
| 103 | +| `add_reply_to_pull_request_comment` | `github_add_reply_to_pull_request_comment` | Répondre à un commentaire de PR | | ||
| 104 | +| `add_pull_request_comment` | `github_add_pull_request_comment` | Ajouter un commentaire de PR | | ||
| 105 | +| `get_copilot_job_status` | `github_get_copilot_job_status` | Obtenir l'état d'un job Copilot | | ||
| 106 | +| `request_copilot_review` | `github_request_copilot_review` | Demander une revue Copilot | | ||
| 107 | + | ||
| 108 | +#### Outils de recherche | ||
| 109 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 110 | +|----------------|-------------|-------------| | ||
| 111 | +| `search_code` | `github_search_code` | Rechercher du code | | ||
| 112 | +| `search_issues` | `github_search_issues` | Rechercher des issues | | ||
| 113 | +| `search_pull_requests` | `github_search_pull_requests` | Rechercher des PRs | | ||
| 114 | +| `search_repositories` | `github_search_repositories` | Rechercher des dépôts | | ||
| 115 | +| `search_commits` | `github_search_commits` | Rechercher des commits | | ||
| 116 | +| `search_users` | `github_search_users` | Rechercher des utilisateurs | | ||
| 117 | + | ||
| 118 | +#### Outils de labels et de jalons | ||
| 119 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 120 | +|----------------|-------------|-------------| | ||
| 121 | +| `get_label` | `github_get_label` | Obtenir un label spécifique | | ||
| 122 | +| `get_team_members` | `github_get_team_members` | Obtenir les membres d'une équipe | | ||
| 123 | +| `get_teams` | `github_get_teams` | Obtenir les équipes de l'utilisateur | | ||
| 124 | +| `list_issue_fields` | `github_list_issue_fields` | Lister les champs d'issue | | ||
| 125 | +| `list_issue_types` | `github_list_issue_types` | Lister les types d'issue | | ||
| 126 | +| `list_releases` | `github_list_releases` | Lister les versions | | ||
| 127 | +| `list_tags` | `github_list_tags` | Lister les tags git | | ||
| 128 | +| `get_release_by_tag` | `github_get_release_by_tag` | Obtenir une version par tag | | ||
| 129 | +| `get_latest_release` | `github_get_latest_release` | Obtenir la dernière version | | ||
| 130 | +| `get_tag` | `github_get_tag` | Obtenir les détails d'un tag | | ||
| 131 | + | ||
| 132 | +#### Outils Copilot | ||
| 133 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 134 | +|----------------|-------------|-------------| | ||
| 135 | +| `create_pull_request_with_copilot` | `github_create_pull_request_with_copilot` | Créer une PR avec Copilot | | ||
| 136 | +| `assign_copilot_to_issue` | `github_assign_copilot_to_issue` | Assigner Copilot à une issue | | ||
| 137 | + | ||
| 138 | +#### Outils de sous-issues | ||
| 139 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 140 | +|----------------|-------------|-------------| | ||
| 141 | +| `sub_issue_write` | `github_sub_issue_write` | Ajouter/supprimer/reprioritiser des sous-issues | | ||
| 142 | + | ||
| 143 | +#### Analyse de secrets | ||
| 144 | +| Nom de l'outil | Nom préfixé | Description | | ||
| 145 | +|----------------|-------------|-------------| | ||
| 146 | +| `run_secret_scanning` | `github_run_secret_scanning` | Exécuter l'analyse de secrets | | ||
| 147 | + | ||
| 148 | +## Préfixage des noms d'outils | ||
| 149 | + | ||
| 150 | +Tous les outils MCP sont préfixés avec le nom de leur serveur pour éviter les collisions de noms. Le format est : | ||
| 151 | + | ||
| 152 | +``` | ||
| 153 | +{nom_serveur}_{nom_original_outil} | ||
| 154 | +``` | ||
| 155 | + | ||
| 156 | +Par exemple : | ||
| 157 | +- Serveur : `wikipedia`, Outil : `search_wikipedia` → Préfixé : `wikipedia_search_wikipedia` | ||
| 158 | +- Serveur : `github`, Outil : `list_repositories` → Préfixé : `github_list_repositories` | ||
| 159 | +- Serveur : `context7`, Outil : `resolve-library-id` → Préfixé : `context7_resolve_library_id` (remarque : les tirets deviennent des underscores) | ||
| 160 | + | ||
| 161 | +## Nettoyage des noms d'outils | ||
| 162 | + | ||
| 163 | +Les noms d'outils sont nettoyés pour être des identifiants Go valides : | ||
| 164 | + | ||
| 165 | +1. Les caractères non alphanumériques sont remplacés par des underscores (`_`) | ||
| 166 | +2. Les underscores de début/fin sont supprimés | ||
| 167 | +3. Les underscores consécutifs multiples sont réduits à un seul | ||
| 168 | +4. Si le résultat est vide, il devient `"tool"` | ||
| 169 | + | ||
| 170 | +Exemples : | ||
| 171 | +- `resolve-library-id` → `resolve_library_id` | ||
| 172 | +- `query-docs` → `query_docs` | ||
| 173 | +- `add-comment_to-pending-review` → `add_comment_to_pending_review` | ||
| 174 | + | ||
| 175 | +## Paramètres requis | ||
| 176 | + | ||
| 177 | +Chaque outil a son propre schéma d'entrée définissant les paramètres requis et optionnels. Les paramètres requis doivent être fournis lors de l'appel de l'outil. | ||
| 178 | + | ||
| 179 | +Pour voir les paramètres requis pour un outil spécifique, utilisez : | ||
| 180 | +``` | ||
| 181 | +Utilisez l'outil {nom_outil} | ||
| 182 | +``` | ||
| 183 | + | ||
| 184 | +Mini-me vous affichera la description de l'outil incluant les paramètres requis. | ||
| 185 | + | ||
| 186 | +## Filtrage des outils | ||
| 187 | + | ||
| 188 | +Vous pouvez filtrer les outils d'un serveur qui sont exposés en spécifiant une liste `tools` dans la configuration du serveur : | ||
| 189 | + | ||
| 190 | +```yaml | ||
| 191 | +servers: | ||
| 192 | + - name: github | ||
| 193 | + transport: http | ||
| 194 | + url: https://api.githubcopilot.com/mcp/ | ||
| 195 | + auth: | ||
| 196 | + type: bearer | ||
| 197 | + tokenEnv: GITHUB_PERSONAL_ACCESS_TOKEN | ||
| 198 | + tools: # Seuls ces outils seront disponibles | ||
| 199 | + - get_me | ||
| 200 | + - list_repositories | ||
| 201 | + - search_code | ||
| 202 | +``` | ||
| 203 | + | ||
| 204 | +## Filtrage des outils binaires | ||
| 205 | + | ||
| 206 | +Mini-me **ne prend en charge que les entrées/sorties texte** pour les outils MCP. Tout outil qui nécessite des données binaires (images, audio, etc.) est automatiquement filtré et un avertissement est affiché au démarrage. | ||
| 207 | + | ||
| 208 | +Cci est une mesure de sécurité pour garantir la compatibilité avec l'architecture texte uniquement de mini-me. | ||
| 209 | + | ||
| 210 | +## Exécution des outils | ||
| 211 | + | ||
| 212 | +Lorsque vous utilisez un outil MCP : | ||
| 213 | + | ||
| 214 | +1. **Vérification des permissions :** Si les permissions sont définies sur `ask`, il vous sera demandé de confirmer | ||
| 215 | +2. **Validation des entrées :** Les paramètres requis sont validés | ||
| 216 | +3. **Délai d'attente :** Le délai d'attente configuré du serveur s'applique | ||
| 217 | +4. **Sortie :** Les résultats sont retournés sous forme de texte (le contenu non texte est ignoré) | ||
| 218 | +5. **Troncature :** La sortie peut être tronquée si elle dépasse `maxOutput` caractères | ||
| 219 | + | ||
| 220 | +## Voir aussi | ||
| 221 | + | ||
| 222 | +- [Configuration MCP](configuration.md) | ||
| 223 | +- [Architecture MCP](explanation/architecture.md) | ||
added
docs/fr/tutorials/mcp-premiers-pas.md +76 -0 | new file mode 100644 | ||
| @@ -0,0 +1,76 @@ | ||
| 1 | +# Tutoriel : Premiers pas avec MCP dans mini-me | |
| 2 | + | |
| 3 | +À la fin de ce tutoriel, vous aurez configuré et testé avec succès votre premier serveur MCP avec mini-me. | |
| 4 | + | |
| 5 | +Aucune connaissance préalable de MCP n'est requise. | |
| 6 | + | |
| 7 | +## Prérequis | |
| 8 | +- mini-me compilé et fonctionnel | |
| 9 | +- Un terminal | |
| 10 | +- Une connexion internet (pour les serveurs distants) | |
| 11 | + | |
| 12 | +## Étape 1 — Vérifier la configuration existante | |
| 13 | + | |
| 14 | +Ouvrez le fichier de configuration fourni : | |
| 15 | +```bash | |
| 16 | +read_file demo/agent.dmr.mcp.yaml | |
| 17 | +``` | |
| 18 | +Vous devriez voir une section `mcp:` déjà configurée. | |
| 19 | + | |
| 20 | +## Étape 2 — Exporter vos clés API | |
| 21 | + | |
| 22 | +Pour utiliser les serveurs nécessitant une authentification, exportez vos clés : | |
| 23 | +```bash | |
| 24 | +export CONTEXT7_API_KEY="votre-clé-context7" | |
| 25 | +export GITHUB_PERSONAL_ACCESS_TOKEN="votre-token-github" | |
| 26 | +``` | |
| 27 | + | |
| 28 | +> ✅ **Note** : Wikipedia ne nécessite pas de clé API. | |
| 29 | + | |
| 30 | +## Étape 3 — Lancer mini-me avec MCP activé | |
| 31 | + | |
| 32 | +Exécutez mini-me avec le fichier de configuration fourni : | |
| 33 | +```bash | |
| 34 | +./mm -config demo/agent.dmr.mcp.yaml | |
| 35 | +``` | |
| 36 | + | |
| 37 | +Vous devriez voir dans la bannière de démarrage : | |
| 38 | +``` | |
| 39 | +tools: bash, read_skill, read_file, write_file, edit_file, plan, context7_resolve_library_id, context7_query_docs, wikipedia_search_wikipedia, wikipedia_get_article, wikipedia_get_summary, ... | |
| 40 | +``` | |
| 41 | + | |
| 42 | +> ✅ **Résultat attendu** : Vous voyez les outils MCP (`context7_*` et `wikipedia_*`) dans la liste. | |
| 43 | + | |
| 44 | +## Étape 4 — Tester avec Wikipedia (sans authentification) | |
| 45 | + | |
| 46 | +Demandez au modèle : | |
| 47 | +``` | |
| 48 | +"Explique-moi ce qu'est le Model Context Protocol" | |
| 49 | +``` | |
| 50 | + | |
| 51 | +> ✅ **Résultat attendu** : Le modèle utilise `wikipedia_search_wikipedia` ou `wikipedia_get_summary` pour trouver des informations. | |
| 52 | + | |
| 53 | +## Étape 5 — Tester avec Context7 (avec authentification) | |
| 54 | + | |
| 55 | +Si vous avez exporté `CONTEXT7_API_KEY`, demandez au modèle : | |
| 56 | +``` | |
| 57 | +"Comment utiliser useEffect dans React ?" | |
| 58 | +``` | |
| 59 | + | |
| 60 | +> ✅ **Résultat attendu** : Le modèle utilise `context7_resolve_library_id` puis `context7_query_docs` pour trouver la documentation. | |
| 61 | + | |
| 62 | +## Étape 6 — Tester avec GitHub (avec authentification) | |
| 63 | + | |
| 64 | +Si vous avez exporté `GITHUB_PERSONAL_ACCESS_TOKEN`, demandez au modèle : | |
| 65 | +``` | |
| 66 | +"Quels sont mes dépôts sur GitHub ?" | |
| 67 | +``` | |
| 68 | + | |
| 69 | +> ✅ **Résultat attendu** : Le modèle utilise les outils `github_*` pour accéder à vos dépôts. | |
| 70 | + | |
| 71 | +## Que faire maintenant ? | |
| 72 | + | |
| 73 | +Vous avez configuré avec succès MCP dans mini-me. Pour aller plus loin : | |
| 74 | +- Pour accomplir des tâches spécifiques → consultez les [guides pratiques](../how-to/) | |
| 75 | +- Pour comprendre le fonctionnement → consultez les [explications](../explanation/) | |
| 76 | +- Pour la référence technique → consultez la [référence](../reference/) | |
| new file mode 100644 | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | +# Tutoriel : Premiers pas avec MCP dans mini-me | ||
| 2 | + | ||
| 3 | +À la fin de ce tutoriel, vous aurez configuré et testé avec succès votre premier serveur MCP avec mini-me. | ||
| 4 | + | ||
| 5 | +Aucune connaissance préalable de MCP n'est requise. | ||
| 6 | + | ||
| 7 | +## Prérequis | ||
| 8 | +- mini-me compilé et fonctionnel | ||
| 9 | +- Un terminal | ||
| 10 | +- Une connexion internet (pour les serveurs distants) | ||
| 11 | + | ||
| 12 | +## Étape 1 — Vérifier la configuration existante | ||
| 13 | + | ||
| 14 | +Ouvrez le fichier de configuration fourni : | ||
| 15 | +```bash | ||
| 16 | +read_file demo/agent.dmr.mcp.yaml | ||
| 17 | +``` | ||
| 18 | +Vous devriez voir une section `mcp:` déjà configurée. | ||
| 19 | + | ||
| 20 | +## Étape 2 — Exporter vos clés API | ||
| 21 | + | ||
| 22 | +Pour utiliser les serveurs nécessitant une authentification, exportez vos clés : | ||
| 23 | +```bash | ||
| 24 | +export CONTEXT7_API_KEY="votre-clé-context7" | ||
| 25 | +export GITHUB_PERSONAL_ACCESS_TOKEN="votre-token-github" | ||
| 26 | +``` | ||
| 27 | + | ||
| 28 | +> ✅ **Note** : Wikipedia ne nécessite pas de clé API. | ||
| 29 | + | ||
| 30 | +## Étape 3 — Lancer mini-me avec MCP activé | ||
| 31 | + | ||
| 32 | +Exécutez mini-me avec le fichier de configuration fourni : | ||
| 33 | +```bash | ||
| 34 | +./mm -config demo/agent.dmr.mcp.yaml | ||
| 35 | +``` | ||
| 36 | + | ||
| 37 | +Vous devriez voir dans la bannière de démarrage : | ||
| 38 | +``` | ||
| 39 | +tools: bash, read_skill, read_file, write_file, edit_file, plan, context7_resolve_library_id, context7_query_docs, wikipedia_search_wikipedia, wikipedia_get_article, wikipedia_get_summary, ... | ||
| 40 | +``` | ||
| 41 | + | ||
| 42 | +> ✅ **Résultat attendu** : Vous voyez les outils MCP (`context7_*` et `wikipedia_*`) dans la liste. | ||
| 43 | + | ||
| 44 | +## Étape 4 — Tester avec Wikipedia (sans authentification) | ||
| 45 | + | ||
| 46 | +Demandez au modèle : | ||
| 47 | +``` | ||
| 48 | +"Explique-moi ce qu'est le Model Context Protocol" | ||
| 49 | +``` | ||
| 50 | + | ||
| 51 | +> ✅ **Résultat attendu** : Le modèle utilise `wikipedia_search_wikipedia` ou `wikipedia_get_summary` pour trouver des informations. | ||
| 52 | + | ||
| 53 | +## Étape 5 — Tester avec Context7 (avec authentification) | ||
| 54 | + | ||
| 55 | +Si vous avez exporté `CONTEXT7_API_KEY`, demandez au modèle : | ||
| 56 | +``` | ||
| 57 | +"Comment utiliser useEffect dans React ?" | ||
| 58 | +``` | ||
| 59 | + | ||
| 60 | +> ✅ **Résultat attendu** : Le modèle utilise `context7_resolve_library_id` puis `context7_query_docs` pour trouver la documentation. | ||
| 61 | + | ||
| 62 | +## Étape 6 — Tester avec GitHub (avec authentification) | ||
| 63 | + | ||
| 64 | +Si vous avez exporté `GITHUB_PERSONAL_ACCESS_TOKEN`, demandez au modèle : | ||
| 65 | +``` | ||
| 66 | +"Quels sont mes dépôts sur GitHub ?" | ||
| 67 | +``` | ||
| 68 | + | ||
| 69 | +> ✅ **Résultat attendu** : Le modèle utilise les outils `github_*` pour accéder à vos dépôts. | ||
| 70 | + | ||
| 71 | +## Que faire maintenant ? | ||
| 72 | + | ||
| 73 | +Vous avez configuré avec succès MCP dans mini-me. Pour aller plus loin : | ||
| 74 | +- Pour accomplir des tâches spécifiques → consultez les [guides pratiques](../how-to/) | ||
| 75 | +- Pour comprendre le fonctionnement → consultez les [explications](../explanation/) | ||
| 76 | +- Pour la référence technique → consultez la [référence](../reference/) | ||
modified
go.mod +1 -0 | @@ -17,6 +17,7 @@ require ( | ||
| 17 | 17 | github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect |
| 18 | 18 | github.com/lucasb-eyer/go-colorful v1.4.0 // indirect |
| 19 | 19 | github.com/rivo/uniseg v0.4.7 // indirect |
| 20 | + github.com/sashabaranov/go-openai v1.42.1 // indirect | |
| 20 | 21 | golang.org/x/term v0.39.0 // indirect |
| 21 | 22 | golang.org/x/text v0.36.0 // indirect |
| 22 | 23 | ) |
| @@ -17,6 +17,7 @@ require ( | |||
| 17 | github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect | 17 | github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect |
| 18 | github.com/lucasb-eyer/go-colorful v1.4.0 // indirect | 18 | github.com/lucasb-eyer/go-colorful v1.4.0 // indirect |
| 19 | github.com/rivo/uniseg v0.4.7 // indirect | 19 | github.com/rivo/uniseg v0.4.7 // indirect |
| 20 | + github.com/sashabaranov/go-openai v1.42.1 // indirect | ||
| 20 | golang.org/x/term v0.39.0 // indirect | 21 | golang.org/x/term v0.39.0 // indirect |
| 21 | golang.org/x/text v0.36.0 // indirect | 22 | golang.org/x/text v0.36.0 // indirect |
| 22 | ) | 23 | ) |
modified
go.sum +2 -0 | @@ -50,6 +50,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||
| 50 | 50 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= |
| 51 | 51 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= |
| 52 | 52 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= |
| 53 | +github.com/sashabaranov/go-openai v1.42.1 h1:9nK2UgDVVSIyoEUNDeWqu3Ttj8EqCO6FT8HK0Cv8VEo= | |
| 54 | +github.com/sashabaranov/go-openai v1.42.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= | |
| 53 | 55 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
| 54 | 56 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
| 55 | 57 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= |
| @@ -50,6 +50,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | |||
| 50 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | 50 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= |
| 51 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= | 51 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= |
| 52 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= | 52 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= |
| 53 | +github.com/sashabaranov/go-openai v1.42.1 h1:9nK2UgDVVSIyoEUNDeWqu3Ttj8EqCO6FT8HK0Cv8VEo= | ||
| 54 | +github.com/sashabaranov/go-openai v1.42.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= | ||
| 53 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | 55 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
| 54 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | 56 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
| 55 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= | 57 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= |
modified
internal/config/config.go +134 -0 | @@ -129,6 +129,11 @@ type Config struct { | ||
| 129 | 129 | // on a machine that lacks it. |
| 130 | 130 | LSP LSPConfig `yaml:"lsp"` |
| 131 | 131 | |
| 132 | + // MCP: Model Context Protocol client settings. Disabled by default. | |
| 133 | + // Allows connecting to MCP servers (stdio or HTTP) and exposing their tools | |
| 134 | + // as wrapped mini-me tools that respect all safety contracts. | |
| 135 | + MCP MCPConfig `yaml:"mcp"` | |
| 136 | + | |
| 132 | 137 | // Permissions: whether the window mode asks before `bash` and the file |
| 133 | 138 | // tools run — "ask" shows a/s/r in the box, "auto" (default) runs them as |
| 134 | 139 | // the terminal always has. Only the window mode reads it today; the |
| @@ -183,6 +188,127 @@ func (s LSPServer) Serves(path string) bool { | ||
| 183 | 188 | return false |
| 184 | 189 | } |
| 185 | 190 | |
| 191 | +// MCPConfig is the `mcp:` block of the YAML. | |
| 192 | +type MCPConfig struct { | |
| 193 | + // Enabled: whether MCP client support is active. Disabled by default. | |
| 194 | + Enabled bool `yaml:"enabled"` | |
| 195 | + | |
| 196 | + // Permissions: default permission mode for MCP tools. Can be overridden | |
| 197 | + // per-server. "ask" (default when enabled) or "auto". | |
| 198 | + Permissions string `yaml:"permissions"` | |
| 199 | + | |
| 200 | + // DiscoveryTimeout: max time to connect and list tools for all servers at startup. | |
| 201 | + // Default: 5s. A timeout means MCP tools are not declared. | |
| 202 | + DiscoveryTimeout time.Duration `yaml:"discoveryTimeout"` | |
| 203 | + | |
| 204 | + // Servers: list of MCP servers to connect to. | |
| 205 | + Servers []MCPServer `yaml:"servers"` | |
| 206 | +} | |
| 207 | + | |
| 208 | +// MCPServer describes one MCP server configuration. | |
| 209 | +type MCPServer struct { | |
| 210 | + // Name: server alias. Used to prefix all tool names from this server | |
| 211 | + // (e.g., a tool "list_files" becomes "context7_list_files" for server "context7"). | |
| 212 | + // Must be a valid identifier (alphanumeric, hyphens, underscores). | |
| 213 | + Name string `yaml:"name"` | |
| 214 | + | |
| 215 | + // Transport: how to connect to the server. "http" for streamable HTTP, | |
| 216 | + // "stdio" for JSON-RPC over stdin/stdout. Required. | |
| 217 | + Transport string `yaml:"transport"` | |
| 218 | + | |
| 219 | + // URL: the HTTP endpoint for streamable HTTP transport. Required when Transport is "http". | |
| 220 | + // Must be a valid URL (e.g., "https://mcp.context7.com/mcp"). | |
| 221 | + URL string `yaml:"url"` | |
| 222 | + | |
| 223 | + // Command: the executable for stdio transport. Required when Transport is "stdio". | |
| 224 | + // Looked up on the PATH at startup. | |
| 225 | + Command string `yaml:"command"` | |
| 226 | + | |
| 227 | + // Args: command-line arguments for stdio transport. | |
| 228 | + Args []string `yaml:"args"` | |
| 229 | + | |
| 230 | + // Env: environment variables for stdio transport. | |
| 231 | + // Values can use Go template syntax with .env to reference environment: | |
| 232 | + // e.g., "{{.env.MY_TOKEN}}" will be replaced with os.Getenv("MY_TOKEN"). | |
| 233 | + Env map[string]string `yaml:"env"` | |
| 234 | + | |
| 235 | + // Timeout: per-request timeout for tool calls. Default: 30s. | |
| 236 | + Timeout time.Duration `yaml:"timeout"` | |
| 237 | + | |
| 238 | + // Auth: authentication configuration for HTTP transport. | |
| 239 | + Auth MCPAuthConfig `yaml:"auth"` | |
| 240 | + | |
| 241 | + // Headers: additional HTTP headers for HTTP transport. | |
| 242 | + Headers map[string]string `yaml:"headers"` | |
| 243 | + | |
| 244 | + // Permissions: override the global permissions setting for this server. | |
| 245 | + // If empty, uses the global mcp.permissions value. | |
| 246 | + Permissions string `yaml:"permissions"` | |
| 247 | + | |
| 248 | + // Tools: optional list of tool names to expose from this server. | |
| 249 | + // If empty, all tools are exposed. Tool names are matched case-insensitively | |
| 250 | + // against the server's tool list. Non-matching names are silently ignored. | |
| 251 | + Tools []string `yaml:"tools"` | |
| 252 | +} | |
| 253 | + | |
| 254 | +// MCPAuthConfig describes authentication settings for an MCP server. | |
| 255 | +type MCPAuthConfig struct { | |
| 256 | + // Type: authentication method. Supported: "none", "bearer", "api_key". | |
| 257 | + // OAuth2 is NOT supported in this initial implementation (per user requirements). | |
| 258 | + Type string `yaml:"type"` | |
| 259 | + | |
| 260 | + // TokenEnv: environment variable name containing the bearer token. | |
| 261 | + // Used when Type is "bearer". | |
| 262 | + TokenEnv string `yaml:"tokenEnv"` | |
| 263 | + | |
| 264 | + // APIKeyEnv: environment variable name containing the API key. | |
| 265 | + // Used when Type is "api_key". | |
| 266 | + APIKeyEnv string `yaml:"apiKeyEnv"` | |
| 267 | + | |
| 268 | + // APIKeyHeader: HTTP header name for the API key. Default: "Authorization". | |
| 269 | + // Some APIs use custom headers like "X-API-Key". | |
| 270 | + APIKeyHeader string `yaml:"apiKeyHeader"` | |
| 271 | +} | |
| 272 | + | |
| 273 | +// Validate checks the MCP server configuration. | |
| 274 | +func (s MCPServer) Validate() error { | |
| 275 | + switch { | |
| 276 | + case s.Name == "": | |
| 277 | + return errors.New("mcp server name must not be empty") | |
| 278 | + case s.Transport == "": | |
| 279 | + return errors.New("mcp server transport must be 'http' or 'stdio'") | |
| 280 | + case s.Transport == "http" && s.URL == "": | |
| 281 | + return errors.New("mcp server with http transport must have a url") | |
| 282 | + case s.Transport == "stdio" && s.Command == "": | |
| 283 | + return errors.New("mcp server with stdio transport must have a command") | |
| 284 | + case s.Transport != "http" && s.Transport != "stdio": | |
| 285 | + return fmt.Errorf("mcp server transport must be 'http' or 'stdio', got %q", s.Transport) | |
| 286 | + case s.Auth.Type != "" && s.Auth.Type != "none" && s.Auth.Type != "bearer" && s.Auth.Type != "api_key": | |
| 287 | + return fmt.Errorf("mcp auth type must be 'none', 'bearer', or 'api_key', got %q", s.Auth.Type) | |
| 288 | + case s.Auth.Type == "bearer" && s.Auth.TokenEnv == "": | |
| 289 | + return errors.New("mcp bearer auth must specify tokenEnv") | |
| 290 | + case s.Auth.Type == "api_key" && s.Auth.APIKeyEnv == "": | |
| 291 | + return errors.New("mcp api_key auth must specify apiKeyEnv") | |
| 292 | + } | |
| 293 | + return nil | |
| 294 | +} | |
| 295 | + | |
| 296 | +// validate checks the MCP configuration. | |
| 297 | +func (m MCPConfig) Validate() error { | |
| 298 | + if !m.Enabled { | |
| 299 | + return nil // MCP disabled, nothing to validate | |
| 300 | + } | |
| 301 | + if m.Permissions != "" && m.Permissions != PermissionsAuto && m.Permissions != PermissionsAsk { | |
| 302 | + return fmt.Errorf("mcp.permissions must be 'auto' or 'ask', got %q", m.Permissions) | |
| 303 | + } | |
| 304 | + for i, s := range m.Servers { | |
| 305 | + if err := s.Validate(); err != nil { | |
| 306 | + return fmt.Errorf("mcp.servers[%d]: %w", i, err) | |
| 307 | + } | |
| 308 | + } | |
| 309 | + return nil | |
| 310 | +} | |
| 311 | + | |
| 186 | 312 | type SessionsConfig struct { |
| 187 | 313 | // Enabled turns the persistence on. Without it nothing is written and |
| 188 | 314 | // ACP does not advertise session/load. |
| @@ -306,6 +432,11 @@ which ones you left running.`, | ||
| 306 | 432 | SummaryMaxTokens: 1200, |
| 307 | 433 | ShowStats: true, |
| 308 | 434 | }, |
| 435 | + MCP: MCPConfig{ | |
| 436 | + Enabled: false, | |
| 437 | + Permissions: PermissionsAsk, | |
| 438 | + DiscoveryTimeout: 5 * time.Second, | |
| 439 | + }, | |
| 309 | 440 | } |
| 310 | 441 | |
| 311 | 442 | // Load reads the YAML file on top of the defaults and returns the path actually |
| @@ -437,5 +568,8 @@ func (c Config) validate() error { | ||
| 437 | 568 | // `enabled: true` with no window and no maxMessages would never trigger — |
| 438 | 569 | // but the window may still come from the server's probe at start-up, so |
| 439 | 570 | // that case is a warning in main, not an error here. |
| 571 | + if err := c.MCP.Validate(); err != nil { | |
| 572 | + return err | |
| 573 | + } | |
| 440 | 574 | return c.LSP.validate() |
| 441 | 575 | } |
| @@ -129,6 +129,11 @@ type Config struct { | |||
| 129 | // on a machine that lacks it. | 129 | // on a machine that lacks it. |
| 130 | LSP LSPConfig `yaml:"lsp"` | 130 | LSP LSPConfig `yaml:"lsp"` |
| 131 | 131 | ||
| 132 | + // MCP: Model Context Protocol client settings. Disabled by default. | ||
| 133 | + // Allows connecting to MCP servers (stdio or HTTP) and exposing their tools | ||
| 134 | + // as wrapped mini-me tools that respect all safety contracts. | ||
| 135 | + MCP MCPConfig `yaml:"mcp"` | ||
| 136 | + | ||
| 132 | // Permissions: whether the window mode asks before `bash` and the file | 137 | // Permissions: whether the window mode asks before `bash` and the file |
| 133 | // tools run — "ask" shows a/s/r in the box, "auto" (default) runs them as | 138 | // tools run — "ask" shows a/s/r in the box, "auto" (default) runs them as |
| 134 | // the terminal always has. Only the window mode reads it today; the | 139 | // the terminal always has. Only the window mode reads it today; the |
| @@ -183,6 +188,127 @@ func (s LSPServer) Serves(path string) bool { | |||
| 183 | return false | 188 | return false |
| 184 | } | 189 | } |
| 185 | 190 | ||
| 191 | +// MCPConfig is the `mcp:` block of the YAML. | ||
| 192 | +type MCPConfig struct { | ||
| 193 | + // Enabled: whether MCP client support is active. Disabled by default. | ||
| 194 | + Enabled bool `yaml:"enabled"` | ||
| 195 | + | ||
| 196 | + // Permissions: default permission mode for MCP tools. Can be overridden | ||
| 197 | + // per-server. "ask" (default when enabled) or "auto". | ||
| 198 | + Permissions string `yaml:"permissions"` | ||
| 199 | + | ||
| 200 | + // DiscoveryTimeout: max time to connect and list tools for all servers at startup. | ||
| 201 | + // Default: 5s. A timeout means MCP tools are not declared. | ||
| 202 | + DiscoveryTimeout time.Duration `yaml:"discoveryTimeout"` | ||
| 203 | + | ||
| 204 | + // Servers: list of MCP servers to connect to. | ||
| 205 | + Servers []MCPServer `yaml:"servers"` | ||
| 206 | +} | ||
| 207 | + | ||
| 208 | +// MCPServer describes one MCP server configuration. | ||
| 209 | +type MCPServer struct { | ||
| 210 | + // Name: server alias. Used to prefix all tool names from this server | ||
| 211 | + // (e.g., a tool "list_files" becomes "context7_list_files" for server "context7"). | ||
| 212 | + // Must be a valid identifier (alphanumeric, hyphens, underscores). | ||
| 213 | + Name string `yaml:"name"` | ||
| 214 | + | ||
| 215 | + // Transport: how to connect to the server. "http" for streamable HTTP, | ||
| 216 | + // "stdio" for JSON-RPC over stdin/stdout. Required. | ||
| 217 | + Transport string `yaml:"transport"` | ||
| 218 | + | ||
| 219 | + // URL: the HTTP endpoint for streamable HTTP transport. Required when Transport is "http". | ||
| 220 | + // Must be a valid URL (e.g., "https://mcp.context7.com/mcp"). | ||
| 221 | + URL string `yaml:"url"` | ||
| 222 | + | ||
| 223 | + // Command: the executable for stdio transport. Required when Transport is "stdio". | ||
| 224 | + // Looked up on the PATH at startup. | ||
| 225 | + Command string `yaml:"command"` | ||
| 226 | + | ||
| 227 | + // Args: command-line arguments for stdio transport. | ||
| 228 | + Args []string `yaml:"args"` | ||
| 229 | + | ||
| 230 | + // Env: environment variables for stdio transport. | ||
| 231 | + // Values can use Go template syntax with .env to reference environment: | ||
| 232 | + // e.g., "{{.env.MY_TOKEN}}" will be replaced with os.Getenv("MY_TOKEN"). | ||
| 233 | + Env map[string]string `yaml:"env"` | ||
| 234 | + | ||
| 235 | + // Timeout: per-request timeout for tool calls. Default: 30s. | ||
| 236 | + Timeout time.Duration `yaml:"timeout"` | ||
| 237 | + | ||
| 238 | + // Auth: authentication configuration for HTTP transport. | ||
| 239 | + Auth MCPAuthConfig `yaml:"auth"` | ||
| 240 | + | ||
| 241 | + // Headers: additional HTTP headers for HTTP transport. | ||
| 242 | + Headers map[string]string `yaml:"headers"` | ||
| 243 | + | ||
| 244 | + // Permissions: override the global permissions setting for this server. | ||
| 245 | + // If empty, uses the global mcp.permissions value. | ||
| 246 | + Permissions string `yaml:"permissions"` | ||
| 247 | + | ||
| 248 | + // Tools: optional list of tool names to expose from this server. | ||
| 249 | + // If empty, all tools are exposed. Tool names are matched case-insensitively | ||
| 250 | + // against the server's tool list. Non-matching names are silently ignored. | ||
| 251 | + Tools []string `yaml:"tools"` | ||
| 252 | +} | ||
| 253 | + | ||
| 254 | +// MCPAuthConfig describes authentication settings for an MCP server. | ||
| 255 | +type MCPAuthConfig struct { | ||
| 256 | + // Type: authentication method. Supported: "none", "bearer", "api_key". | ||
| 257 | + // OAuth2 is NOT supported in this initial implementation (per user requirements). | ||
| 258 | + Type string `yaml:"type"` | ||
| 259 | + | ||
| 260 | + // TokenEnv: environment variable name containing the bearer token. | ||
| 261 | + // Used when Type is "bearer". | ||
| 262 | + TokenEnv string `yaml:"tokenEnv"` | ||
| 263 | + | ||
| 264 | + // APIKeyEnv: environment variable name containing the API key. | ||
| 265 | + // Used when Type is "api_key". | ||
| 266 | + APIKeyEnv string `yaml:"apiKeyEnv"` | ||
| 267 | + | ||
| 268 | + // APIKeyHeader: HTTP header name for the API key. Default: "Authorization". | ||
| 269 | + // Some APIs use custom headers like "X-API-Key". | ||
| 270 | + APIKeyHeader string `yaml:"apiKeyHeader"` | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +// Validate checks the MCP server configuration. | ||
| 274 | +func (s MCPServer) Validate() error { | ||
| 275 | + switch { | ||
| 276 | + case s.Name == "": | ||
| 277 | + return errors.New("mcp server name must not be empty") | ||
| 278 | + case s.Transport == "": | ||
| 279 | + return errors.New("mcp server transport must be 'http' or 'stdio'") | ||
| 280 | + case s.Transport == "http" && s.URL == "": | ||
| 281 | + return errors.New("mcp server with http transport must have a url") | ||
| 282 | + case s.Transport == "stdio" && s.Command == "": | ||
| 283 | + return errors.New("mcp server with stdio transport must have a command") | ||
| 284 | + case s.Transport != "http" && s.Transport != "stdio": | ||
| 285 | + return fmt.Errorf("mcp server transport must be 'http' or 'stdio', got %q", s.Transport) | ||
| 286 | + case s.Auth.Type != "" && s.Auth.Type != "none" && s.Auth.Type != "bearer" && s.Auth.Type != "api_key": | ||
| 287 | + return fmt.Errorf("mcp auth type must be 'none', 'bearer', or 'api_key', got %q", s.Auth.Type) | ||
| 288 | + case s.Auth.Type == "bearer" && s.Auth.TokenEnv == "": | ||
| 289 | + return errors.New("mcp bearer auth must specify tokenEnv") | ||
| 290 | + case s.Auth.Type == "api_key" && s.Auth.APIKeyEnv == "": | ||
| 291 | + return errors.New("mcp api_key auth must specify apiKeyEnv") | ||
| 292 | + } | ||
| 293 | + return nil | ||
| 294 | +} | ||
| 295 | + | ||
| 296 | +// validate checks the MCP configuration. | ||
| 297 | +func (m MCPConfig) Validate() error { | ||
| 298 | + if !m.Enabled { | ||
| 299 | + return nil // MCP disabled, nothing to validate | ||
| 300 | + } | ||
| 301 | + if m.Permissions != "" && m.Permissions != PermissionsAuto && m.Permissions != PermissionsAsk { | ||
| 302 | + return fmt.Errorf("mcp.permissions must be 'auto' or 'ask', got %q", m.Permissions) | ||
| 303 | + } | ||
| 304 | + for i, s := range m.Servers { | ||
| 305 | + if err := s.Validate(); err != nil { | ||
| 306 | + return fmt.Errorf("mcp.servers[%d]: %w", i, err) | ||
| 307 | + } | ||
| 308 | + } | ||
| 309 | + return nil | ||
| 310 | +} | ||
| 311 | + | ||
| 186 | type SessionsConfig struct { | 312 | type SessionsConfig struct { |
| 187 | // Enabled turns the persistence on. Without it nothing is written and | 313 | // Enabled turns the persistence on. Without it nothing is written and |
| 188 | // ACP does not advertise session/load. | 314 | // ACP does not advertise session/load. |
| @@ -306,6 +432,11 @@ which ones you left running.`, | |||
| 306 | SummaryMaxTokens: 1200, | 432 | SummaryMaxTokens: 1200, |
| 307 | ShowStats: true, | 433 | ShowStats: true, |
| 308 | }, | 434 | }, |
| 435 | + MCP: MCPConfig{ | ||
| 436 | + Enabled: false, | ||
| 437 | + Permissions: PermissionsAsk, | ||
| 438 | + DiscoveryTimeout: 5 * time.Second, | ||
| 439 | + }, | ||
| 309 | } | 440 | } |
| 310 | 441 | ||
| 311 | // Load reads the YAML file on top of the defaults and returns the path actually | 442 | // Load reads the YAML file on top of the defaults and returns the path actually |
| @@ -437,5 +568,8 @@ func (c Config) validate() error { | |||
| 437 | // `enabled: true` with no window and no maxMessages would never trigger — | 568 | // `enabled: true` with no window and no maxMessages would never trigger — |
| 438 | // but the window may still come from the server's probe at start-up, so | 569 | // but the window may still come from the server's probe at start-up, so |
| 439 | // that case is a warning in main, not an error here. | 570 | // that case is a warning in main, not an error here. |
| 571 | + if err := c.MCP.Validate(); err != nil { | ||
| 572 | + return err | ||
| 573 | + } | ||
| 440 | return c.LSP.validate() | 574 | return c.LSP.validate() |
| 441 | } | 575 | } |
added
internal/mcp/client.go +517 -0 | new file mode 100644 | ||
| @@ -0,0 +1,517 @@ | ||
| 1 | +// Package mcp provides MCP (Model Context Protocol) client functionality for mini-me. | |
| 2 | +// It supports HTTP transport (streamable MCP) with text-only tool invocation. | |
| 3 | +// Stdio transport is NOT yet implemented. | |
| 4 | +package mcp | |
| 5 | + | |
| 6 | +import ( | |
| 7 | + "bufio" | |
| 8 | + "bytes" | |
| 9 | + "context" | |
| 10 | + "encoding/json" | |
| 11 | + "fmt" | |
| 12 | + "io" | |
| 13 | + "net/http" | |
| 14 | + "os" | |
| 15 | + "strings" | |
| 16 | + "time" | |
| 17 | + | |
| 18 | + "mm/internal/config" | |
| 19 | +) | |
| 20 | + | |
| 21 | +// Tool represents a discovered MCP tool. | |
| 22 | +type Tool struct { | |
| 23 | + // Name is the tool's unique identifier on the server. | |
| 24 | + Name string `json:"name"` | |
| 25 | + // Description is the human-readable description. | |
| 26 | + Description string `json:"description"` | |
| 27 | + // InputSchema describes the expected input parameters. | |
| 28 | + InputSchema ToolInputSchema `json:"inputSchema"` | |
| 29 | + // OutputSchema describes the expected output. | |
| 30 | + OutputSchema ToolOutputSchema `json:"outputSchema"` | |
| 31 | + // Annotations contains additional metadata about the tool (e.g., hints, titles) | |
| 32 | + // This field is optional and may not be present in all MCP server responses. | |
| 33 | + Annotations map[string]any `json:"annotations,omitempty"` | |
| 34 | + // Icons contains icon data for the tool (light/dark mode) | |
| 35 | + // This field is optional and may not be present in all MCP server responses. | |
| 36 | + Icons []ToolIcon `json:"icons,omitempty"` | |
| 37 | +} | |
| 38 | + | |
| 39 | +// ToolInputSchema describes the input parameters for a tool. | |
| 40 | +type ToolInputSchema struct { | |
| 41 | + Type string `json:"type"` | |
| 42 | + Properties map[string]Param `json:"properties"` | |
| 43 | + Required []string `json:"required,omitempty"` | |
| 44 | +} | |
| 45 | + | |
| 46 | +// ToolOutputSchema describes the output of a tool. | |
| 47 | +type ToolOutputSchema struct { | |
| 48 | + Type string `json:"type"` | |
| 49 | + Properties map[string]Param `json:"properties"` | |
| 50 | +} | |
| 51 | + | |
| 52 | +// ToolIcon represents an icon for a tool (used for UI display). | |
| 53 | +type ToolIcon struct { | |
| 54 | + Src string `json:"src"` // Base64-encoded image data or URL | |
| 55 | + MimeType string `json:"mimeType"` // MIME type of the icon (e.g., "image/png") | |
| 56 | + Theme string `json:"theme"` // Theme variant (e.g., "light", "dark") | |
| 57 | +} | |
| 58 | + | |
| 59 | +// Param describes a single parameter or output field. | |
| 60 | +type Param struct { | |
| 61 | + Type string `json:"type"` | |
| 62 | + Description string `json:"description,omitempty"` | |
| 63 | + // For binary detection | |
| 64 | + Format string `json:"format,omitempty"` | |
| 65 | + // MCP-specific header hint (e.g., "x-mcp-header": "owner") | |
| 66 | + MCPHeader string `json:"x-mcp-header,omitempty"` | |
| 67 | +} | |
| 68 | + | |
| 69 | +// IsTextOnly returns true if the tool only uses text/JSON I/O (no binary). | |
| 70 | +func (t Tool) IsTextOnly() bool { | |
| 71 | + // Check input parameters | |
| 72 | + for _, p := range t.InputSchema.Properties { | |
| 73 | + if p.Type == "string" && p.Format == "binary" { | |
| 74 | + return false | |
| 75 | + } | |
| 76 | + if p.Type == "binary" { | |
| 77 | + return false | |
| 78 | + } | |
| 79 | + } | |
| 80 | + // Check output schema | |
| 81 | + for _, p := range t.OutputSchema.Properties { | |
| 82 | + if p.Type == "string" && p.Format == "binary" { | |
| 83 | + return false | |
| 84 | + } | |
| 85 | + if p.Type == "binary" { | |
| 86 | + return false | |
| 87 | + } | |
| 88 | + } | |
| 89 | + return true | |
| 90 | +} | |
| 91 | + | |
| 92 | +// Client is the interface for MCP server clients. | |
| 93 | +type Client interface { | |
| 94 | + // ListTools returns all tools available from this server. | |
| 95 | + ListTools(ctx context.Context) ([]Tool, error) | |
| 96 | + // CallTool invokes a specific tool with the given arguments. | |
| 97 | + CallTool(ctx context.Context, toolName string, args map[string]any) (CallToolResult, error) | |
| 98 | + // Close cleans up the client's resources. | |
| 99 | + Close() error | |
| 100 | +} | |
| 101 | + | |
| 102 | +// CallToolResult is the result of a tool call. | |
| 103 | +type CallToolResult struct { | |
| 104 | + Content []Content `json:"content"` | |
| 105 | + IsError bool `json:"isError"` | |
| 106 | +} | |
| 107 | + | |
| 108 | +// Content represents a content block in a tool result. | |
| 109 | +type Content struct { | |
| 110 | + Type string `json:"type"` // "text", "image", etc. | |
| 111 | + Text string `json:"text,omitempty"` | |
| 112 | +} | |
| 113 | + | |
| 114 | +// ServerClient wraps a Client with its configuration. | |
| 115 | +type ServerClient struct { | |
| 116 | + Client Client | |
| 117 | + Server config.MCPServer | |
| 118 | + ToolList []Tool // Cached tool list from discovery | |
| 119 | +} | |
| 120 | + | |
| 121 | +// ListTools delegates to the underlying Client. | |
| 122 | +func (sc *ServerClient) ListTools(ctx context.Context) ([]Tool, error) { | |
| 123 | + return sc.Client.ListTools(ctx) | |
| 124 | +} | |
| 125 | + | |
| 126 | +// CallTool delegates to the underlying Client. | |
| 127 | +func (sc *ServerClient) CallTool(ctx context.Context, toolName string, args map[string]any) (CallToolResult, error) { | |
| 128 | + return sc.Client.CallTool(ctx, toolName, args) | |
| 129 | +} | |
| 130 | + | |
| 131 | +// Close delegates to the underlying Client. | |
| 132 | +func (sc *ServerClient) Close() error { | |
| 133 | + return sc.Client.Close() | |
| 134 | +} | |
| 135 | + | |
| 136 | +// Connect creates a client for the given server configuration. | |
| 137 | +func Connect(server config.MCPServer) (*ServerClient, error) { | |
| 138 | + switch server.Transport { | |
| 139 | + case "http": | |
| 140 | + client, err := NewHTTPClient(server) | |
| 141 | + if err != nil { | |
| 142 | + return nil, err | |
| 143 | + } | |
| 144 | + return &ServerClient{Client: client, Server: server}, nil | |
| 145 | + case "stdio": | |
| 146 | + // Stdio transport is NOT yet implemented | |
| 147 | + return nil, fmt.Errorf("stdio transport for MCP is not yet implemented - use HTTP transport") | |
| 148 | + default: | |
| 149 | + return nil, fmt.Errorf("unknown transport type: %s", server.Transport) | |
| 150 | + } | |
| 151 | +} | |
| 152 | + | |
| 153 | +// ConnectAll creates clients for all servers and performs discovery. | |
| 154 | +// Returns a map of server name to ServerClient, with tool lists populated. | |
| 155 | +func ConnectAll(ctx context.Context, servers []config.MCPServer) (map[string]*ServerClient, []string, error) { | |
| 156 | + clients := make(map[string]*ServerClient) | |
| 157 | + var warnings []string | |
| 158 | + | |
| 159 | + for _, server := range servers { | |
| 160 | + // Validate server config | |
| 161 | + if err := server.Validate(); err != nil { | |
| 162 | + warnings = append(warnings, fmt.Sprintf("mcp server %s: %v", server.Name, err)) | |
| 163 | + continue | |
| 164 | + } | |
| 165 | + | |
| 166 | + // Create client | |
| 167 | + client, err := Connect(server) | |
| 168 | + if err != nil { | |
| 169 | + warnings = append(warnings, fmt.Sprintf("mcp server %s: %v", server.Name, err)) | |
| 170 | + continue | |
| 171 | + } | |
| 172 | + | |
| 173 | + // Perform discovery | |
| 174 | + tools, err := client.ListTools(ctx) | |
| 175 | + if err != nil { | |
| 176 | + client.Close() | |
| 177 | + warnings = append(warnings, fmt.Sprintf("mcp server %s: discovery failed: %v", server.Name, err)) | |
| 178 | + continue | |
| 179 | + } | |
| 180 | + | |
| 181 | + // Filter by allowed tools if specified | |
| 182 | + if len(server.Tools) > 0 { | |
| 183 | + allowed := make(map[string]bool, len(server.Tools)) | |
| 184 | + for _, name := range server.Tools { | |
| 185 | + allowed[strings.ToLower(name)] = true | |
| 186 | + } | |
| 187 | + var filtered []Tool | |
| 188 | + for _, tool := range tools { | |
| 189 | + if allowed[strings.ToLower(tool.Name)] { | |
| 190 | + filtered = append(filtered, tool) | |
| 191 | + } | |
| 192 | + } | |
| 193 | + tools = filtered | |
| 194 | + } | |
| 195 | + | |
| 196 | + // Filter out binary tools | |
| 197 | + var textTools []Tool | |
| 198 | + for _, tool := range tools { | |
| 199 | + if !tool.IsTextOnly() { | |
| 200 | + warnings = append(warnings, | |
| 201 | + fmt.Sprintf("mcp server %s: tool %s uses binary I/O - skipped (text-only)", | |
| 202 | + server.Name, tool.Name)) | |
| 203 | + continue | |
| 204 | + } | |
| 205 | + textTools = append(textTools, tool) | |
| 206 | + } | |
| 207 | + | |
| 208 | + client.ToolList = textTools | |
| 209 | + clients[server.Name] = client | |
| 210 | + } | |
| 211 | + | |
| 212 | + return clients, warnings, nil | |
| 213 | +} | |
| 214 | + | |
| 215 | +// CloseAll closes all clients. | |
| 216 | +func CloseAll(clients map[string]*ServerClient) { | |
| 217 | + for _, sc := range clients { | |
| 218 | + sc.Close() | |
| 219 | + } | |
| 220 | +} | |
| 221 | + | |
| 222 | +// ============================================================================= | |
| 223 | +// HTTP Client Implementation | |
| 224 | +// ============================================================================= | |
| 225 | + | |
| 226 | +// HTTPClient implements Client for HTTP transport (streamable MCP). | |
| 227 | +type HTTPClient struct { | |
| 228 | + server config.MCPServer | |
| 229 | + httpClient *http.Client | |
| 230 | + baseURL string | |
| 231 | +} | |
| 232 | + | |
| 233 | +// NewHTTPClient creates a new HTTP client for the given server. | |
| 234 | +func NewHTTPClient(server config.MCPServer) (*HTTPClient, error) { | |
| 235 | + // Validate URL | |
| 236 | + if server.URL == "" { | |
| 237 | + return nil, fmt.Errorf("http transport requires a url") | |
| 238 | + } | |
| 239 | + | |
| 240 | + // Ensure URL has a path component | |
| 241 | + url := strings.TrimSpace(server.URL) | |
| 242 | + if !strings.Contains(url, "/") { | |
| 243 | + // Assume it's just a host and append /mcp | |
| 244 | + url = url + "/mcp" | |
| 245 | + } | |
| 246 | + | |
| 247 | + return &HTTPClient{ | |
| 248 | + server: server, | |
| 249 | + httpClient: &http.Client{ | |
| 250 | + Timeout: 30 * time.Second, | |
| 251 | + }, | |
| 252 | + baseURL: url, | |
| 253 | + }, nil | |
| 254 | +} | |
| 255 | + | |
| 256 | +// mcpRequest represents an MCP JSON-RPC request. | |
| 257 | +type mcpRequest struct { | |
| 258 | + JSONRPC string `json:"jsonrpc"` | |
| 259 | + ID int `json:"id"` | |
| 260 | + Method string `json:"method"` | |
| 261 | + Params interface{} `json:"params,omitempty"` | |
| 262 | +} | |
| 263 | + | |
| 264 | +// mcpResponse represents an MCP JSON-RPC response. | |
| 265 | +type mcpResponse struct { | |
| 266 | + JSONRPC string `json:"jsonrpc"` | |
| 267 | + ID int `json:"id"` | |
| 268 | + Result json.RawMessage `json:"result,omitempty"` | |
| 269 | + Error *mcpError `json:"error,omitempty"` | |
| 270 | +} | |
| 271 | + | |
| 272 | +// mcpError represents an MCP error. | |
| 273 | +type mcpError struct { | |
| 274 | + Code int `json:"code"` | |
| 275 | + Message string `json:"message"` | |
| 276 | +} | |
| 277 | + | |
| 278 | +// toolsListRequest represents a tools/list request. | |
| 279 | +type toolsListRequest struct { | |
| 280 | +} | |
| 281 | + | |
| 282 | +// toolsListResponse represents a tools/list response. | |
| 283 | +type toolsListResponse struct { | |
| 284 | + Tools []Tool `json:"tools"` | |
| 285 | +} | |
| 286 | + | |
| 287 | +// callToolRequest represents a tools/call request. | |
| 288 | +type callToolRequest struct { | |
| 289 | + Name string `json:"name"` | |
| 290 | + Args map[string]any `json:"arguments"` | |
| 291 | +} | |
| 292 | + | |
| 293 | +// callToolResponse represents a tools/call response. | |
| 294 | +type callToolResponse struct { | |
| 295 | + Content []Content `json:"content"` | |
| 296 | + IsError bool `json:"isError"` | |
| 297 | +} | |
| 298 | + | |
| 299 | +// ListTools returns all tools from the HTTP server. | |
| 300 | +func (c *HTTPClient) ListTools(ctx context.Context) ([]Tool, error) { | |
| 301 | + request := mcpRequest{ | |
| 302 | + JSONRPC: "2.0", | |
| 303 | + ID: 1, | |
| 304 | + Method: "tools/list", | |
| 305 | + Params: toolsListRequest{}, | |
| 306 | + } | |
| 307 | + | |
| 308 | + resp, err := c.doRequest(ctx, request) | |
| 309 | + if err != nil { | |
| 310 | + return nil, err | |
| 311 | + } | |
| 312 | + | |
| 313 | + var listResp toolsListResponse | |
| 314 | + if err := json.Unmarshal(resp.Result, &listResp); err != nil { | |
| 315 | + return nil, fmt.Errorf("failed to parse tools/list response: %w", err) | |
| 316 | + } | |
| 317 | + | |
| 318 | + return listResp.Tools, nil | |
| 319 | +} | |
| 320 | + | |
| 321 | +// CallTool invokes a tool via HTTP. | |
| 322 | +func (c *HTTPClient) CallTool(ctx context.Context, toolName string, args map[string]any) (CallToolResult, error) { | |
| 323 | + request := mcpRequest{ | |
| 324 | + JSONRPC: "2.0", | |
| 325 | + ID: 2, | |
| 326 | + Method: "tools/call", | |
| 327 | + Params: callToolRequest{ | |
| 328 | + Name: toolName, | |
| 329 | + Args: args, | |
| 330 | + }, | |
| 331 | + } | |
| 332 | + | |
| 333 | + resp, err := c.doRequest(ctx, request) | |
| 334 | + if err != nil { | |
| 335 | + return CallToolResult{IsError: true}, err | |
| 336 | + } | |
| 337 | + | |
| 338 | + // Parse the response - we need to handle both single content and array | |
| 339 | + var callResp callToolResponse | |
| 340 | + if err := json.Unmarshal(resp.Result, &callResp); err != nil { | |
| 341 | + // Try parsing as a single content item wrapped in an object | |
| 342 | + var singleContent struct { | |
| 343 | + Content Content `json:"content"` | |
| 344 | + IsError bool `json:"isError"` | |
| 345 | + } | |
| 346 | + if parseErr := json.Unmarshal(resp.Result, &singleContent); parseErr == nil { | |
| 347 | + return CallToolResult{ | |
| 348 | + Content: []Content{singleContent.Content}, | |
| 349 | + IsError: singleContent.IsError, | |
| 350 | + }, nil | |
| 351 | + } | |
| 352 | + return CallToolResult{IsError: true}, fmt.Errorf("failed to parse tools/call response: %w", err) | |
| 353 | + } | |
| 354 | + | |
| 355 | + return CallToolResult{ | |
| 356 | + Content: callResp.Content, | |
| 357 | + IsError: callResp.IsError, | |
| 358 | + }, nil | |
| 359 | +} | |
| 360 | + | |
| 361 | +// doRequest sends a JSON-RPC request and returns the response. | |
| 362 | +// For streamable HTTP MCP, the response is a Server-Sent Events (SSE) stream. | |
| 363 | +func (c *HTTPClient) doRequest(ctx context.Context, req mcpRequest) (*mcpResponse, error) { | |
| 364 | + // Marshal request | |
| 365 | + body, err := json.Marshal(req) | |
| 366 | + if err != nil { | |
| 367 | + return nil, err | |
| 368 | + } | |
| 369 | + | |
| 370 | + // Create HTTP request | |
| 371 | + httpReq, err := http.NewRequestWithContext(ctx, "POST", c.baseURL, bytes.NewReader(body)) | |
| 372 | + if err != nil { | |
| 373 | + return nil, err | |
| 374 | + } | |
| 375 | + | |
| 376 | + // Set headers | |
| 377 | + httpReq.Header.Set("Content-Type", "application/json") | |
| 378 | + | |
| 379 | + // Set Accept header - support both JSON and event-stream for streamable HTTP MCP | |
| 380 | + // If custom Accept header is provided in config, it will override this | |
| 381 | + httpReq.Header.Set("Accept", "application/json, text/event-stream") | |
| 382 | + | |
| 383 | + // Add auth headers | |
| 384 | + if err := c.addAuthHeaders(httpReq); err != nil { | |
| 385 | + return nil, err | |
| 386 | + } | |
| 387 | + | |
| 388 | + // Add custom headers (these will override defaults) | |
| 389 | + for k, v := range c.server.Headers { | |
| 390 | + httpReq.Header.Set(k, v) | |
| 391 | + } | |
| 392 | + | |
| 393 | + // Send request | |
| 394 | + httpResp, err := c.httpClient.Do(httpReq) | |
| 395 | + if err != nil { | |
| 396 | + return nil, err | |
| 397 | + } | |
| 398 | + defer httpResp.Body.Close() | |
| 399 | + | |
| 400 | + // Check content type to determine if it's SSE or plain JSON | |
| 401 | + contentType := httpResp.Header.Get("Content-Type") | |
| 402 | + | |
| 403 | + // For streamable HTTP MCP, the response is a Server-Sent Events (SSE) stream | |
| 404 | + // We need to parse the SSE events to extract the JSON-RPC messages | |
| 405 | + // Check if the request Accept header includes text/event-stream | |
| 406 | + acceptHeader := httpReq.Header.Get("Accept") | |
| 407 | + if strings.Contains(contentType, "text/event-stream") || | |
| 408 | + strings.Contains(acceptHeader, "text/event-stream") { | |
| 409 | + return c.parseSSEResponse(httpResp.Body) | |
| 410 | + } | |
| 411 | + | |
| 412 | + // For regular JSON responses, read and parse directly | |
| 413 | + respBody, err := io.ReadAll(httpResp.Body) | |
| 414 | + if err != nil { | |
| 415 | + return nil, err | |
| 416 | + } | |
| 417 | + | |
| 418 | + // Parse response | |
| 419 | + var mcpResp mcpResponse | |
| 420 | + if err := json.Unmarshal(respBody, &mcpResp); err != nil { | |
| 421 | + return nil, fmt.Errorf("failed to parse response: %w", err) | |
| 422 | + } | |
| 423 | + | |
| 424 | + // Check for error | |
| 425 | + if mcpResp.Error != nil { | |
| 426 | + return &mcpResp, fmt.Errorf("mcp error: %s (code: %d)", mcpResp.Error.Message, mcpResp.Error.Code) | |
| 427 | + } | |
| 428 | + | |
| 429 | + return &mcpResp, nil | |
| 430 | +} | |
| 431 | + | |
| 432 | +// parseSSEResponse parses a Server-Sent Events stream and returns the first JSON-RPC response. | |
| 433 | +// For streamable HTTP MCP, responses are sent as SSE events with "event: message" and "data: <json>" | |
| 434 | +// Note: Some MCP servers (like GitHub) send very large responses with base64-encoded icons, | |
| 435 | +// so we use a custom scanner with a larger buffer to handle long lines. | |
| 436 | +func (c *HTTPClient) parseSSEResponse(body io.Reader) (*mcpResponse, error) { | |
| 437 | + // Create a reader with a larger buffer to handle long data lines | |
| 438 | + // GitHub MCP server sends large responses with base64-encoded icons | |
| 439 | + const maxLineSize = 10 * 1024 * 1024 // 10 MB max line size | |
| 440 | + reader := bufio.NewReaderSize(body, maxLineSize) | |
| 441 | + | |
| 442 | + for { | |
| 443 | + // Read a line manually | |
| 444 | + line, err := reader.ReadString('\n') | |
| 445 | + if err != nil { | |
| 446 | + if err == io.EOF { | |
| 447 | + break | |
| 448 | + } | |
| 449 | + return nil, fmt.Errorf("failed to read SSE stream: %w", err) | |
| 450 | + } | |
| 451 | + | |
| 452 | + line = strings.TrimSpace(line) | |
| 453 | + | |
| 454 | + // Skip empty lines | |
| 455 | + if line == "" { | |
| 456 | + continue | |
| 457 | + } | |
| 458 | + | |
| 459 | + // Check if this is a data line (SSE format: "data: <json>") | |
| 460 | + if strings.HasPrefix(line, "data: ") { | |
| 461 | + // Extract the JSON data after "data: " prefix | |
| 462 | + jsonData := strings.TrimPrefix(line, "data: ") | |
| 463 | + | |
| 464 | + // Parse the JSON-RPC response | |
| 465 | + var mcpResp mcpResponse | |
| 466 | + if err := json.Unmarshal([]byte(jsonData), &mcpResp); err != nil { | |
| 467 | + // Try parsing the line directly in case it's a complete JSON object | |
| 468 | + // (some SSE implementations send JSON directly without data: prefix) | |
| 469 | + if err2 := json.Unmarshal([]byte(line), &mcpResp); err2 != nil { | |
| 470 | + continue // Not valid JSON, skip this line | |
| 471 | + } | |
| 472 | + } | |
| 473 | + | |
| 474 | + // Check for error in response | |
| 475 | + if mcpResp.Error != nil { | |
| 476 | + return &mcpResp, fmt.Errorf("mcp error: %s (code: %d)", mcpResp.Error.Message, mcpResp.Error.Code) | |
| 477 | + } | |
| 478 | + | |
| 479 | + // Successfully parsed a response | |
| 480 | + return &mcpResp, nil | |
| 481 | + } | |
| 482 | + } | |
| 483 | + | |
| 484 | + return nil, fmt.Errorf("no valid JSON-RPC response found in SSE stream") | |
| 485 | +} | |
| 486 | + | |
| 487 | +// addAuthHeaders adds authentication headers to the request. | |
| 488 | +func (c *HTTPClient) addAuthHeaders(req *http.Request) error { | |
| 489 | + switch c.server.Auth.Type { | |
| 490 | + case "none", "": | |
| 491 | + return nil | |
| 492 | + case "bearer": | |
| 493 | + token := os.Getenv(c.server.Auth.TokenEnv) | |
| 494 | + if token == "" { | |
| 495 | + return fmt.Errorf("missing bearer token for env var %s", c.server.Auth.TokenEnv) | |
| 496 | + } | |
| 497 | + req.Header.Set("Authorization", "Bearer "+token) | |
| 498 | + case "api_key": | |
| 499 | + key := os.Getenv(c.server.Auth.APIKeyEnv) | |
| 500 | + if key == "" { | |
| 501 | + return fmt.Errorf("missing API key for env var %s", c.server.Auth.APIKeyEnv) | |
| 502 | + } | |
| 503 | + header := c.server.Auth.APIKeyHeader | |
| 504 | + if header == "" { | |
| 505 | + header = "Authorization" | |
| 506 | + } | |
| 507 | + req.Header.Set(header, key) | |
| 508 | + default: | |
| 509 | + return fmt.Errorf("unknown auth type: %s", c.server.Auth.Type) | |
| 510 | + } | |
| 511 | + return nil | |
| 512 | +} | |
| 513 | + | |
| 514 | +// Close cleans up the HTTP client. | |
| 515 | +func (c *HTTPClient) Close() error { | |
| 516 | + return nil | |
| 517 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,517 @@ | |||
| 1 | +// Package mcp provides MCP (Model Context Protocol) client functionality for mini-me. | ||
| 2 | +// It supports HTTP transport (streamable MCP) with text-only tool invocation. | ||
| 3 | +// Stdio transport is NOT yet implemented. | ||
| 4 | +package mcp | ||
| 5 | + | ||
| 6 | +import ( | ||
| 7 | + "bufio" | ||
| 8 | + "bytes" | ||
| 9 | + "context" | ||
| 10 | + "encoding/json" | ||
| 11 | + "fmt" | ||
| 12 | + "io" | ||
| 13 | + "net/http" | ||
| 14 | + "os" | ||
| 15 | + "strings" | ||
| 16 | + "time" | ||
| 17 | + | ||
| 18 | + "mm/internal/config" | ||
| 19 | +) | ||
| 20 | + | ||
| 21 | +// Tool represents a discovered MCP tool. | ||
| 22 | +type Tool struct { | ||
| 23 | + // Name is the tool's unique identifier on the server. | ||
| 24 | + Name string `json:"name"` | ||
| 25 | + // Description is the human-readable description. | ||
| 26 | + Description string `json:"description"` | ||
| 27 | + // InputSchema describes the expected input parameters. | ||
| 28 | + InputSchema ToolInputSchema `json:"inputSchema"` | ||
| 29 | + // OutputSchema describes the expected output. | ||
| 30 | + OutputSchema ToolOutputSchema `json:"outputSchema"` | ||
| 31 | + // Annotations contains additional metadata about the tool (e.g., hints, titles) | ||
| 32 | + // This field is optional and may not be present in all MCP server responses. | ||
| 33 | + Annotations map[string]any `json:"annotations,omitempty"` | ||
| 34 | + // Icons contains icon data for the tool (light/dark mode) | ||
| 35 | + // This field is optional and may not be present in all MCP server responses. | ||
| 36 | + Icons []ToolIcon `json:"icons,omitempty"` | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +// ToolInputSchema describes the input parameters for a tool. | ||
| 40 | +type ToolInputSchema struct { | ||
| 41 | + Type string `json:"type"` | ||
| 42 | + Properties map[string]Param `json:"properties"` | ||
| 43 | + Required []string `json:"required,omitempty"` | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +// ToolOutputSchema describes the output of a tool. | ||
| 47 | +type ToolOutputSchema struct { | ||
| 48 | + Type string `json:"type"` | ||
| 49 | + Properties map[string]Param `json:"properties"` | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +// ToolIcon represents an icon for a tool (used for UI display). | ||
| 53 | +type ToolIcon struct { | ||
| 54 | + Src string `json:"src"` // Base64-encoded image data or URL | ||
| 55 | + MimeType string `json:"mimeType"` // MIME type of the icon (e.g., "image/png") | ||
| 56 | + Theme string `json:"theme"` // Theme variant (e.g., "light", "dark") | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +// Param describes a single parameter or output field. | ||
| 60 | +type Param struct { | ||
| 61 | + Type string `json:"type"` | ||
| 62 | + Description string `json:"description,omitempty"` | ||
| 63 | + // For binary detection | ||
| 64 | + Format string `json:"format,omitempty"` | ||
| 65 | + // MCP-specific header hint (e.g., "x-mcp-header": "owner") | ||
| 66 | + MCPHeader string `json:"x-mcp-header,omitempty"` | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +// IsTextOnly returns true if the tool only uses text/JSON I/O (no binary). | ||
| 70 | +func (t Tool) IsTextOnly() bool { | ||
| 71 | + // Check input parameters | ||
| 72 | + for _, p := range t.InputSchema.Properties { | ||
| 73 | + if p.Type == "string" && p.Format == "binary" { | ||
| 74 | + return false | ||
| 75 | + } | ||
| 76 | + if p.Type == "binary" { | ||
| 77 | + return false | ||
| 78 | + } | ||
| 79 | + } | ||
| 80 | + // Check output schema | ||
| 81 | + for _, p := range t.OutputSchema.Properties { | ||
| 82 | + if p.Type == "string" && p.Format == "binary" { | ||
| 83 | + return false | ||
| 84 | + } | ||
| 85 | + if p.Type == "binary" { | ||
| 86 | + return false | ||
| 87 | + } | ||
| 88 | + } | ||
| 89 | + return true | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +// Client is the interface for MCP server clients. | ||
| 93 | +type Client interface { | ||
| 94 | + // ListTools returns all tools available from this server. | ||
| 95 | + ListTools(ctx context.Context) ([]Tool, error) | ||
| 96 | + // CallTool invokes a specific tool with the given arguments. | ||
| 97 | + CallTool(ctx context.Context, toolName string, args map[string]any) (CallToolResult, error) | ||
| 98 | + // Close cleans up the client's resources. | ||
| 99 | + Close() error | ||
| 100 | +} | ||
| 101 | + | ||
| 102 | +// CallToolResult is the result of a tool call. | ||
| 103 | +type CallToolResult struct { | ||
| 104 | + Content []Content `json:"content"` | ||
| 105 | + IsError bool `json:"isError"` | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +// Content represents a content block in a tool result. | ||
| 109 | +type Content struct { | ||
| 110 | + Type string `json:"type"` // "text", "image", etc. | ||
| 111 | + Text string `json:"text,omitempty"` | ||
| 112 | +} | ||
| 113 | + | ||
| 114 | +// ServerClient wraps a Client with its configuration. | ||
| 115 | +type ServerClient struct { | ||
| 116 | + Client Client | ||
| 117 | + Server config.MCPServer | ||
| 118 | + ToolList []Tool // Cached tool list from discovery | ||
| 119 | +} | ||
| 120 | + | ||
| 121 | +// ListTools delegates to the underlying Client. | ||
| 122 | +func (sc *ServerClient) ListTools(ctx context.Context) ([]Tool, error) { | ||
| 123 | + return sc.Client.ListTools(ctx) | ||
| 124 | +} | ||
| 125 | + | ||
| 126 | +// CallTool delegates to the underlying Client. | ||
| 127 | +func (sc *ServerClient) CallTool(ctx context.Context, toolName string, args map[string]any) (CallToolResult, error) { | ||
| 128 | + return sc.Client.CallTool(ctx, toolName, args) | ||
| 129 | +} | ||
| 130 | + | ||
| 131 | +// Close delegates to the underlying Client. | ||
| 132 | +func (sc *ServerClient) Close() error { | ||
| 133 | + return sc.Client.Close() | ||
| 134 | +} | ||
| 135 | + | ||
| 136 | +// Connect creates a client for the given server configuration. | ||
| 137 | +func Connect(server config.MCPServer) (*ServerClient, error) { | ||
| 138 | + switch server.Transport { | ||
| 139 | + case "http": | ||
| 140 | + client, err := NewHTTPClient(server) | ||
| 141 | + if err != nil { | ||
| 142 | + return nil, err | ||
| 143 | + } | ||
| 144 | + return &ServerClient{Client: client, Server: server}, nil | ||
| 145 | + case "stdio": | ||
| 146 | + // Stdio transport is NOT yet implemented | ||
| 147 | + return nil, fmt.Errorf("stdio transport for MCP is not yet implemented - use HTTP transport") | ||
| 148 | + default: | ||
| 149 | + return nil, fmt.Errorf("unknown transport type: %s", server.Transport) | ||
| 150 | + } | ||
| 151 | +} | ||
| 152 | + | ||
| 153 | +// ConnectAll creates clients for all servers and performs discovery. | ||
| 154 | +// Returns a map of server name to ServerClient, with tool lists populated. | ||
| 155 | +func ConnectAll(ctx context.Context, servers []config.MCPServer) (map[string]*ServerClient, []string, error) { | ||
| 156 | + clients := make(map[string]*ServerClient) | ||
| 157 | + var warnings []string | ||
| 158 | + | ||
| 159 | + for _, server := range servers { | ||
| 160 | + // Validate server config | ||
| 161 | + if err := server.Validate(); err != nil { | ||
| 162 | + warnings = append(warnings, fmt.Sprintf("mcp server %s: %v", server.Name, err)) | ||
| 163 | + continue | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + // Create client | ||
| 167 | + client, err := Connect(server) | ||
| 168 | + if err != nil { | ||
| 169 | + warnings = append(warnings, fmt.Sprintf("mcp server %s: %v", server.Name, err)) | ||
| 170 | + continue | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + // Perform discovery | ||
| 174 | + tools, err := client.ListTools(ctx) | ||
| 175 | + if err != nil { | ||
| 176 | + client.Close() | ||
| 177 | + warnings = append(warnings, fmt.Sprintf("mcp server %s: discovery failed: %v", server.Name, err)) | ||
| 178 | + continue | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + // Filter by allowed tools if specified | ||
| 182 | + if len(server.Tools) > 0 { | ||
| 183 | + allowed := make(map[string]bool, len(server.Tools)) | ||
| 184 | + for _, name := range server.Tools { | ||
| 185 | + allowed[strings.ToLower(name)] = true | ||
| 186 | + } | ||
| 187 | + var filtered []Tool | ||
| 188 | + for _, tool := range tools { | ||
| 189 | + if allowed[strings.ToLower(tool.Name)] { | ||
| 190 | + filtered = append(filtered, tool) | ||
| 191 | + } | ||
| 192 | + } | ||
| 193 | + tools = filtered | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + // Filter out binary tools | ||
| 197 | + var textTools []Tool | ||
| 198 | + for _, tool := range tools { | ||
| 199 | + if !tool.IsTextOnly() { | ||
| 200 | + warnings = append(warnings, | ||
| 201 | + fmt.Sprintf("mcp server %s: tool %s uses binary I/O - skipped (text-only)", | ||
| 202 | + server.Name, tool.Name)) | ||
| 203 | + continue | ||
| 204 | + } | ||
| 205 | + textTools = append(textTools, tool) | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + client.ToolList = textTools | ||
| 209 | + clients[server.Name] = client | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + return clients, warnings, nil | ||
| 213 | +} | ||
| 214 | + | ||
| 215 | +// CloseAll closes all clients. | ||
| 216 | +func CloseAll(clients map[string]*ServerClient) { | ||
| 217 | + for _, sc := range clients { | ||
| 218 | + sc.Close() | ||
| 219 | + } | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +// ============================================================================= | ||
| 223 | +// HTTP Client Implementation | ||
| 224 | +// ============================================================================= | ||
| 225 | + | ||
| 226 | +// HTTPClient implements Client for HTTP transport (streamable MCP). | ||
| 227 | +type HTTPClient struct { | ||
| 228 | + server config.MCPServer | ||
| 229 | + httpClient *http.Client | ||
| 230 | + baseURL string | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | +// NewHTTPClient creates a new HTTP client for the given server. | ||
| 234 | +func NewHTTPClient(server config.MCPServer) (*HTTPClient, error) { | ||
| 235 | + // Validate URL | ||
| 236 | + if server.URL == "" { | ||
| 237 | + return nil, fmt.Errorf("http transport requires a url") | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + // Ensure URL has a path component | ||
| 241 | + url := strings.TrimSpace(server.URL) | ||
| 242 | + if !strings.Contains(url, "/") { | ||
| 243 | + // Assume it's just a host and append /mcp | ||
| 244 | + url = url + "/mcp" | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + return &HTTPClient{ | ||
| 248 | + server: server, | ||
| 249 | + httpClient: &http.Client{ | ||
| 250 | + Timeout: 30 * time.Second, | ||
| 251 | + }, | ||
| 252 | + baseURL: url, | ||
| 253 | + }, nil | ||
| 254 | +} | ||
| 255 | + | ||
| 256 | +// mcpRequest represents an MCP JSON-RPC request. | ||
| 257 | +type mcpRequest struct { | ||
| 258 | + JSONRPC string `json:"jsonrpc"` | ||
| 259 | + ID int `json:"id"` | ||
| 260 | + Method string `json:"method"` | ||
| 261 | + Params interface{} `json:"params,omitempty"` | ||
| 262 | +} | ||
| 263 | + | ||
| 264 | +// mcpResponse represents an MCP JSON-RPC response. | ||
| 265 | +type mcpResponse struct { | ||
| 266 | + JSONRPC string `json:"jsonrpc"` | ||
| 267 | + ID int `json:"id"` | ||
| 268 | + Result json.RawMessage `json:"result,omitempty"` | ||
| 269 | + Error *mcpError `json:"error,omitempty"` | ||
| 270 | +} | ||
| 271 | + | ||
| 272 | +// mcpError represents an MCP error. | ||
| 273 | +type mcpError struct { | ||
| 274 | + Code int `json:"code"` | ||
| 275 | + Message string `json:"message"` | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +// toolsListRequest represents a tools/list request. | ||
| 279 | +type toolsListRequest struct { | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +// toolsListResponse represents a tools/list response. | ||
| 283 | +type toolsListResponse struct { | ||
| 284 | + Tools []Tool `json:"tools"` | ||
| 285 | +} | ||
| 286 | + | ||
| 287 | +// callToolRequest represents a tools/call request. | ||
| 288 | +type callToolRequest struct { | ||
| 289 | + Name string `json:"name"` | ||
| 290 | + Args map[string]any `json:"arguments"` | ||
| 291 | +} | ||
| 292 | + | ||
| 293 | +// callToolResponse represents a tools/call response. | ||
| 294 | +type callToolResponse struct { | ||
| 295 | + Content []Content `json:"content"` | ||
| 296 | + IsError bool `json:"isError"` | ||
| 297 | +} | ||
| 298 | + | ||
| 299 | +// ListTools returns all tools from the HTTP server. | ||
| 300 | +func (c *HTTPClient) ListTools(ctx context.Context) ([]Tool, error) { | ||
| 301 | + request := mcpRequest{ | ||
| 302 | + JSONRPC: "2.0", | ||
| 303 | + ID: 1, | ||
| 304 | + Method: "tools/list", | ||
| 305 | + Params: toolsListRequest{}, | ||
| 306 | + } | ||
| 307 | + | ||
| 308 | + resp, err := c.doRequest(ctx, request) | ||
| 309 | + if err != nil { | ||
| 310 | + return nil, err | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + var listResp toolsListResponse | ||
| 314 | + if err := json.Unmarshal(resp.Result, &listResp); err != nil { | ||
| 315 | + return nil, fmt.Errorf("failed to parse tools/list response: %w", err) | ||
| 316 | + } | ||
| 317 | + | ||
| 318 | + return listResp.Tools, nil | ||
| 319 | +} | ||
| 320 | + | ||
| 321 | +// CallTool invokes a tool via HTTP. | ||
| 322 | +func (c *HTTPClient) CallTool(ctx context.Context, toolName string, args map[string]any) (CallToolResult, error) { | ||
| 323 | + request := mcpRequest{ | ||
| 324 | + JSONRPC: "2.0", | ||
| 325 | + ID: 2, | ||
| 326 | + Method: "tools/call", | ||
| 327 | + Params: callToolRequest{ | ||
| 328 | + Name: toolName, | ||
| 329 | + Args: args, | ||
| 330 | + }, | ||
| 331 | + } | ||
| 332 | + | ||
| 333 | + resp, err := c.doRequest(ctx, request) | ||
| 334 | + if err != nil { | ||
| 335 | + return CallToolResult{IsError: true}, err | ||
| 336 | + } | ||
| 337 | + | ||
| 338 | + // Parse the response - we need to handle both single content and array | ||
| 339 | + var callResp callToolResponse | ||
| 340 | + if err := json.Unmarshal(resp.Result, &callResp); err != nil { | ||
| 341 | + // Try parsing as a single content item wrapped in an object | ||
| 342 | + var singleContent struct { | ||
| 343 | + Content Content `json:"content"` | ||
| 344 | + IsError bool `json:"isError"` | ||
| 345 | + } | ||
| 346 | + if parseErr := json.Unmarshal(resp.Result, &singleContent); parseErr == nil { | ||
| 347 | + return CallToolResult{ | ||
| 348 | + Content: []Content{singleContent.Content}, | ||
| 349 | + IsError: singleContent.IsError, | ||
| 350 | + }, nil | ||
| 351 | + } | ||
| 352 | + return CallToolResult{IsError: true}, fmt.Errorf("failed to parse tools/call response: %w", err) | ||
| 353 | + } | ||
| 354 | + | ||
| 355 | + return CallToolResult{ | ||
| 356 | + Content: callResp.Content, | ||
| 357 | + IsError: callResp.IsError, | ||
| 358 | + }, nil | ||
| 359 | +} | ||
| 360 | + | ||
| 361 | +// doRequest sends a JSON-RPC request and returns the response. | ||
| 362 | +// For streamable HTTP MCP, the response is a Server-Sent Events (SSE) stream. | ||
| 363 | +func (c *HTTPClient) doRequest(ctx context.Context, req mcpRequest) (*mcpResponse, error) { | ||
| 364 | + // Marshal request | ||
| 365 | + body, err := json.Marshal(req) | ||
| 366 | + if err != nil { | ||
| 367 | + return nil, err | ||
| 368 | + } | ||
| 369 | + | ||
| 370 | + // Create HTTP request | ||
| 371 | + httpReq, err := http.NewRequestWithContext(ctx, "POST", c.baseURL, bytes.NewReader(body)) | ||
| 372 | + if err != nil { | ||
| 373 | + return nil, err | ||
| 374 | + } | ||
| 375 | + | ||
| 376 | + // Set headers | ||
| 377 | + httpReq.Header.Set("Content-Type", "application/json") | ||
| 378 | + | ||
| 379 | + // Set Accept header - support both JSON and event-stream for streamable HTTP MCP | ||
| 380 | + // If custom Accept header is provided in config, it will override this | ||
| 381 | + httpReq.Header.Set("Accept", "application/json, text/event-stream") | ||
| 382 | + | ||
| 383 | + // Add auth headers | ||
| 384 | + if err := c.addAuthHeaders(httpReq); err != nil { | ||
| 385 | + return nil, err | ||
| 386 | + } | ||
| 387 | + | ||
| 388 | + // Add custom headers (these will override defaults) | ||
| 389 | + for k, v := range c.server.Headers { | ||
| 390 | + httpReq.Header.Set(k, v) | ||
| 391 | + } | ||
| 392 | + | ||
| 393 | + // Send request | ||
| 394 | + httpResp, err := c.httpClient.Do(httpReq) | ||
| 395 | + if err != nil { | ||
| 396 | + return nil, err | ||
| 397 | + } | ||
| 398 | + defer httpResp.Body.Close() | ||
| 399 | + | ||
| 400 | + // Check content type to determine if it's SSE or plain JSON | ||
| 401 | + contentType := httpResp.Header.Get("Content-Type") | ||
| 402 | + | ||
| 403 | + // For streamable HTTP MCP, the response is a Server-Sent Events (SSE) stream | ||
| 404 | + // We need to parse the SSE events to extract the JSON-RPC messages | ||
| 405 | + // Check if the request Accept header includes text/event-stream | ||
| 406 | + acceptHeader := httpReq.Header.Get("Accept") | ||
| 407 | + if strings.Contains(contentType, "text/event-stream") || | ||
| 408 | + strings.Contains(acceptHeader, "text/event-stream") { | ||
| 409 | + return c.parseSSEResponse(httpResp.Body) | ||
| 410 | + } | ||
| 411 | + | ||
| 412 | + // For regular JSON responses, read and parse directly | ||
| 413 | + respBody, err := io.ReadAll(httpResp.Body) | ||
| 414 | + if err != nil { | ||
| 415 | + return nil, err | ||
| 416 | + } | ||
| 417 | + | ||
| 418 | + // Parse response | ||
| 419 | + var mcpResp mcpResponse | ||
| 420 | + if err := json.Unmarshal(respBody, &mcpResp); err != nil { | ||
| 421 | + return nil, fmt.Errorf("failed to parse response: %w", err) | ||
| 422 | + } | ||
| 423 | + | ||
| 424 | + // Check for error | ||
| 425 | + if mcpResp.Error != nil { | ||
| 426 | + return &mcpResp, fmt.Errorf("mcp error: %s (code: %d)", mcpResp.Error.Message, mcpResp.Error.Code) | ||
| 427 | + } | ||
| 428 | + | ||
| 429 | + return &mcpResp, nil | ||
| 430 | +} | ||
| 431 | + | ||
| 432 | +// parseSSEResponse parses a Server-Sent Events stream and returns the first JSON-RPC response. | ||
| 433 | +// For streamable HTTP MCP, responses are sent as SSE events with "event: message" and "data: <json>" | ||
| 434 | +// Note: Some MCP servers (like GitHub) send very large responses with base64-encoded icons, | ||
| 435 | +// so we use a custom scanner with a larger buffer to handle long lines. | ||
| 436 | +func (c *HTTPClient) parseSSEResponse(body io.Reader) (*mcpResponse, error) { | ||
| 437 | + // Create a reader with a larger buffer to handle long data lines | ||
| 438 | + // GitHub MCP server sends large responses with base64-encoded icons | ||
| 439 | + const maxLineSize = 10 * 1024 * 1024 // 10 MB max line size | ||
| 440 | + reader := bufio.NewReaderSize(body, maxLineSize) | ||
| 441 | + | ||
| 442 | + for { | ||
| 443 | + // Read a line manually | ||
| 444 | + line, err := reader.ReadString('\n') | ||
| 445 | + if err != nil { | ||
| 446 | + if err == io.EOF { | ||
| 447 | + break | ||
| 448 | + } | ||
| 449 | + return nil, fmt.Errorf("failed to read SSE stream: %w", err) | ||
| 450 | + } | ||
| 451 | + | ||
| 452 | + line = strings.TrimSpace(line) | ||
| 453 | + | ||
| 454 | + // Skip empty lines | ||
| 455 | + if line == "" { | ||
| 456 | + continue | ||
| 457 | + } | ||
| 458 | + | ||
| 459 | + // Check if this is a data line (SSE format: "data: <json>") | ||
| 460 | + if strings.HasPrefix(line, "data: ") { | ||
| 461 | + // Extract the JSON data after "data: " prefix | ||
| 462 | + jsonData := strings.TrimPrefix(line, "data: ") | ||
| 463 | + | ||
| 464 | + // Parse the JSON-RPC response | ||
| 465 | + var mcpResp mcpResponse | ||
| 466 | + if err := json.Unmarshal([]byte(jsonData), &mcpResp); err != nil { | ||
| 467 | + // Try parsing the line directly in case it's a complete JSON object | ||
| 468 | + // (some SSE implementations send JSON directly without data: prefix) | ||
| 469 | + if err2 := json.Unmarshal([]byte(line), &mcpResp); err2 != nil { | ||
| 470 | + continue // Not valid JSON, skip this line | ||
| 471 | + } | ||
| 472 | + } | ||
| 473 | + | ||
| 474 | + // Check for error in response | ||
| 475 | + if mcpResp.Error != nil { | ||
| 476 | + return &mcpResp, fmt.Errorf("mcp error: %s (code: %d)", mcpResp.Error.Message, mcpResp.Error.Code) | ||
| 477 | + } | ||
| 478 | + | ||
| 479 | + // Successfully parsed a response | ||
| 480 | + return &mcpResp, nil | ||
| 481 | + } | ||
| 482 | + } | ||
| 483 | + | ||
| 484 | + return nil, fmt.Errorf("no valid JSON-RPC response found in SSE stream") | ||
| 485 | +} | ||
| 486 | + | ||
| 487 | +// addAuthHeaders adds authentication headers to the request. | ||
| 488 | +func (c *HTTPClient) addAuthHeaders(req *http.Request) error { | ||
| 489 | + switch c.server.Auth.Type { | ||
| 490 | + case "none", "": | ||
| 491 | + return nil | ||
| 492 | + case "bearer": | ||
| 493 | + token := os.Getenv(c.server.Auth.TokenEnv) | ||
| 494 | + if token == "" { | ||
| 495 | + return fmt.Errorf("missing bearer token for env var %s", c.server.Auth.TokenEnv) | ||
| 496 | + } | ||
| 497 | + req.Header.Set("Authorization", "Bearer "+token) | ||
| 498 | + case "api_key": | ||
| 499 | + key := os.Getenv(c.server.Auth.APIKeyEnv) | ||
| 500 | + if key == "" { | ||
| 501 | + return fmt.Errorf("missing API key for env var %s", c.server.Auth.APIKeyEnv) | ||
| 502 | + } | ||
| 503 | + header := c.server.Auth.APIKeyHeader | ||
| 504 | + if header == "" { | ||
| 505 | + header = "Authorization" | ||
| 506 | + } | ||
| 507 | + req.Header.Set(header, key) | ||
| 508 | + default: | ||
| 509 | + return fmt.Errorf("unknown auth type: %s", c.server.Auth.Type) | ||
| 510 | + } | ||
| 511 | + return nil | ||
| 512 | +} | ||
| 513 | + | ||
| 514 | +// Close cleans up the HTTP client. | ||
| 515 | +func (c *HTTPClient) Close() error { | ||
| 516 | + return nil | ||
| 517 | +} | ||
added
internal/mcp/client_test.go +184 -0 | new file mode 100644 | ||
| @@ -0,0 +1,184 @@ | ||
| 1 | +package mcp | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "encoding/json" | |
| 5 | + "testing" | |
| 6 | +) | |
| 7 | + | |
| 8 | +func TestToolParsingWithAnnotationsAndIcons(t *testing.T) { | |
| 9 | + // Test JSON from GitHub MCP server response with extra fields | |
| 10 | + jsonData := `{ | |
| 11 | + "name": "add_comment_to_pending_review", | |
| 12 | + "description": "Add review comment to the requester's latest pending pull request review.", | |
| 13 | + "inputSchema": { | |
| 14 | + "type": "object", | |
| 15 | + "properties": { | |
| 16 | + "body": {"type": "string", "description": "The text of the review comment"} | |
| 17 | + }, | |
| 18 | + "required": ["body"] | |
| 19 | + }, | |
| 20 | + "outputSchema": { | |
| 21 | + "type": "object", | |
| 22 | + "properties": {} | |
| 23 | + }, | |
| 24 | + "annotations": { | |
| 25 | + "idempotentHint": false, | |
| 26 | + "readOnlyHint": false, | |
| 27 | + "title": "Add review comment" | |
| 28 | + }, | |
| 29 | + "icons": [ | |
| 30 | + { | |
| 31 | + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA", | |
| 32 | + "mimeType": "image/png", | |
| 33 | + "theme": "light" | |
| 34 | + }, | |
| 35 | + { | |
| 36 | + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA", | |
| 37 | + "mimeType": "image/png", | |
| 38 | + "theme": "dark" | |
| 39 | + } | |
| 40 | + ] | |
| 41 | + }` | |
| 42 | + | |
| 43 | + var tool Tool | |
| 44 | + err := json.Unmarshal([]byte(jsonData), &tool) | |
| 45 | + if err != nil { | |
| 46 | + t.Fatalf("Failed to parse tool with annotations and icons: %v", err) | |
| 47 | + } | |
| 48 | + | |
| 49 | + // Verify all fields were parsed correctly | |
| 50 | + if tool.Name != "add_comment_to_pending_review" { | |
| 51 | + t.Errorf("Expected name 'add_comment_to_pending_review', got '%s'", tool.Name) | |
| 52 | + } | |
| 53 | + | |
| 54 | + if tool.Description != "Add review comment to the requester's latest pending pull request review." { | |
| 55 | + t.Errorf("Description mismatch") | |
| 56 | + } | |
| 57 | + | |
| 58 | + if tool.InputSchema.Type != "object" { | |
| 59 | + t.Errorf("Expected inputSchema type 'object', got '%s'", tool.InputSchema.Type) | |
| 60 | + } | |
| 61 | + | |
| 62 | + if tool.Annotations == nil { | |
| 63 | + t.Error("Annotations should not be nil") | |
| 64 | + } | |
| 65 | + | |
| 66 | + if len(tool.Icons) != 2 { | |
| 67 | + t.Errorf("Expected 2 icons, got %d", len(tool.Icons)) | |
| 68 | + } | |
| 69 | + | |
| 70 | + if tool.Icons[0].Theme != "light" { | |
| 71 | + t.Errorf("Expected first icon theme 'light', got '%s'", tool.Icons[0].Theme) | |
| 72 | + } | |
| 73 | + | |
| 74 | + if tool.Icons[1].Theme != "dark" { | |
| 75 | + t.Errorf("Expected second icon theme 'dark', got '%s'", tool.Icons[1].Theme) | |
| 76 | + } | |
| 77 | +} | |
| 78 | + | |
| 79 | +func TestToolParsingWithoutOptionalFields(t *testing.T) { | |
| 80 | + // Test that tools without annotations/icons still parse correctly | |
| 81 | + jsonData := `{ | |
| 82 | + "name": "get_me", | |
| 83 | + "description": "Get details of the authenticated GitHub user.", | |
| 84 | + "inputSchema": { | |
| 85 | + "type": "object", | |
| 86 | + "properties": {} | |
| 87 | + }, | |
| 88 | + "outputSchema": { | |
| 89 | + "type": "object", | |
| 90 | + "properties": {} | |
| 91 | + } | |
| 92 | + }` | |
| 93 | + | |
| 94 | + var tool Tool | |
| 95 | + err := json.Unmarshal([]byte(jsonData), &tool) | |
| 96 | + if err != nil { | |
| 97 | + t.Fatalf("Failed to parse tool without optional fields: %v", err) | |
| 98 | + } | |
| 99 | + | |
| 100 | + if tool.Name != "get_me" { | |
| 101 | + t.Errorf("Expected name 'get_me', got '%s'", tool.Name) | |
| 102 | + } | |
| 103 | + | |
| 104 | + if tool.Annotations != nil { | |
| 105 | + t.Error("Annotations should be nil when not present") | |
| 106 | + } | |
| 107 | + | |
| 108 | + if tool.Icons != nil { | |
| 109 | + t.Error("Icons should be nil when not present") | |
| 110 | + } | |
| 111 | +} | |
| 112 | + | |
| 113 | +func TestToolsListResponseParsing(t *testing.T) { | |
| 114 | + // Test parsing the full tools/list response from GitHub MCP server | |
| 115 | + jsonData := `{ | |
| 116 | + "tools": [ | |
| 117 | + { | |
| 118 | + "name": "get_me", | |
| 119 | + "description": "Get details of the authenticated GitHub user.", | |
| 120 | + "inputSchema": { | |
| 121 | + "type": "object", | |
| 122 | + "properties": {} | |
| 123 | + }, | |
| 124 | + "outputSchema": { | |
| 125 | + "type": "object", | |
| 126 | + "properties": {} | |
| 127 | + } | |
| 128 | + }, | |
| 129 | + { | |
| 130 | + "name": "list_issues", | |
| 131 | + "description": "List issues in a GitHub repository.", | |
| 132 | + "inputSchema": { | |
| 133 | + "type": "object", | |
| 134 | + "properties": { | |
| 135 | + "owner": {"type": "string", "description": "Repository owner", "x-mcp-header": "owner"}, | |
| 136 | + "repo": {"type": "string", "description": "Repository name", "x-mcp-header": "repo"} | |
| 137 | + }, | |
| 138 | + "required": ["owner", "repo"] | |
| 139 | + }, | |
| 140 | + "outputSchema": { | |
| 141 | + "type": "object", | |
| 142 | + "properties": {} | |
| 143 | + }, | |
| 144 | + "annotations": { | |
| 145 | + "readOnlyHint": true | |
| 146 | + } | |
| 147 | + } | |
| 148 | + ] | |
| 149 | + }` | |
| 150 | + | |
| 151 | + var response toolsListResponse | |
| 152 | + err := json.Unmarshal([]byte(jsonData), &response) | |
| 153 | + if err != nil { | |
| 154 | + t.Fatalf("Failed to parse toolsListResponse: %v", err) | |
| 155 | + } | |
| 156 | + | |
| 157 | + if len(response.Tools) != 2 { | |
| 158 | + t.Fatalf("Expected 2 tools, got %d", len(response.Tools)) | |
| 159 | + } | |
| 160 | + | |
| 161 | + // Check first tool (no optional fields) | |
| 162 | + if response.Tools[0].Name != "get_me" { | |
| 163 | + t.Errorf("Expected first tool name 'get_me', got '%s'", response.Tools[0].Name) | |
| 164 | + } | |
| 165 | + | |
| 166 | + // Check second tool (with annotations and x-mcp-header) | |
| 167 | + if response.Tools[1].Name != "list_issues" { | |
| 168 | + t.Errorf("Expected second tool name 'list_issues', got '%s'", response.Tools[1].Name) | |
| 169 | + } | |
| 170 | + | |
| 171 | + if response.Tools[1].Annotations == nil { | |
| 172 | + t.Error("Second tool should have annotations") | |
| 173 | + } | |
| 174 | + | |
| 175 | + // Check that x-mcp-header is parsed in inputSchema properties | |
| 176 | + ownerParam, exists := response.Tools[1].InputSchema.Properties["owner"] | |
| 177 | + if !exists { | |
| 178 | + t.Error("owner parameter should exist in inputSchema") | |
| 179 | + } else { | |
| 180 | + if ownerParam.MCPHeader != "owner" { | |
| 181 | + t.Errorf("Expected MCPHeader 'owner', got '%s'", ownerParam.MCPHeader) | |
| 182 | + } | |
| 183 | + } | |
| 184 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,184 @@ | |||
| 1 | +package mcp | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "encoding/json" | ||
| 5 | + "testing" | ||
| 6 | +) | ||
| 7 | + | ||
| 8 | +func TestToolParsingWithAnnotationsAndIcons(t *testing.T) { | ||
| 9 | + // Test JSON from GitHub MCP server response with extra fields | ||
| 10 | + jsonData := `{ | ||
| 11 | + "name": "add_comment_to_pending_review", | ||
| 12 | + "description": "Add review comment to the requester's latest pending pull request review.", | ||
| 13 | + "inputSchema": { | ||
| 14 | + "type": "object", | ||
| 15 | + "properties": { | ||
| 16 | + "body": {"type": "string", "description": "The text of the review comment"} | ||
| 17 | + }, | ||
| 18 | + "required": ["body"] | ||
| 19 | + }, | ||
| 20 | + "outputSchema": { | ||
| 21 | + "type": "object", | ||
| 22 | + "properties": {} | ||
| 23 | + }, | ||
| 24 | + "annotations": { | ||
| 25 | + "idempotentHint": false, | ||
| 26 | + "readOnlyHint": false, | ||
| 27 | + "title": "Add review comment" | ||
| 28 | + }, | ||
| 29 | + "icons": [ | ||
| 30 | + { | ||
| 31 | + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA", | ||
| 32 | + "mimeType": "image/png", | ||
| 33 | + "theme": "light" | ||
| 34 | + }, | ||
| 35 | + { | ||
| 36 | + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA", | ||
| 37 | + "mimeType": "image/png", | ||
| 38 | + "theme": "dark" | ||
| 39 | + } | ||
| 40 | + ] | ||
| 41 | + }` | ||
| 42 | + | ||
| 43 | + var tool Tool | ||
| 44 | + err := json.Unmarshal([]byte(jsonData), &tool) | ||
| 45 | + if err != nil { | ||
| 46 | + t.Fatalf("Failed to parse tool with annotations and icons: %v", err) | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + // Verify all fields were parsed correctly | ||
| 50 | + if tool.Name != "add_comment_to_pending_review" { | ||
| 51 | + t.Errorf("Expected name 'add_comment_to_pending_review', got '%s'", tool.Name) | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + if tool.Description != "Add review comment to the requester's latest pending pull request review." { | ||
| 55 | + t.Errorf("Description mismatch") | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + if tool.InputSchema.Type != "object" { | ||
| 59 | + t.Errorf("Expected inputSchema type 'object', got '%s'", tool.InputSchema.Type) | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + if tool.Annotations == nil { | ||
| 63 | + t.Error("Annotations should not be nil") | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + if len(tool.Icons) != 2 { | ||
| 67 | + t.Errorf("Expected 2 icons, got %d", len(tool.Icons)) | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + if tool.Icons[0].Theme != "light" { | ||
| 71 | + t.Errorf("Expected first icon theme 'light', got '%s'", tool.Icons[0].Theme) | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + if tool.Icons[1].Theme != "dark" { | ||
| 75 | + t.Errorf("Expected second icon theme 'dark', got '%s'", tool.Icons[1].Theme) | ||
| 76 | + } | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +func TestToolParsingWithoutOptionalFields(t *testing.T) { | ||
| 80 | + // Test that tools without annotations/icons still parse correctly | ||
| 81 | + jsonData := `{ | ||
| 82 | + "name": "get_me", | ||
| 83 | + "description": "Get details of the authenticated GitHub user.", | ||
| 84 | + "inputSchema": { | ||
| 85 | + "type": "object", | ||
| 86 | + "properties": {} | ||
| 87 | + }, | ||
| 88 | + "outputSchema": { | ||
| 89 | + "type": "object", | ||
| 90 | + "properties": {} | ||
| 91 | + } | ||
| 92 | + }` | ||
| 93 | + | ||
| 94 | + var tool Tool | ||
| 95 | + err := json.Unmarshal([]byte(jsonData), &tool) | ||
| 96 | + if err != nil { | ||
| 97 | + t.Fatalf("Failed to parse tool without optional fields: %v", err) | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + if tool.Name != "get_me" { | ||
| 101 | + t.Errorf("Expected name 'get_me', got '%s'", tool.Name) | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + if tool.Annotations != nil { | ||
| 105 | + t.Error("Annotations should be nil when not present") | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + if tool.Icons != nil { | ||
| 109 | + t.Error("Icons should be nil when not present") | ||
| 110 | + } | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +func TestToolsListResponseParsing(t *testing.T) { | ||
| 114 | + // Test parsing the full tools/list response from GitHub MCP server | ||
| 115 | + jsonData := `{ | ||
| 116 | + "tools": [ | ||
| 117 | + { | ||
| 118 | + "name": "get_me", | ||
| 119 | + "description": "Get details of the authenticated GitHub user.", | ||
| 120 | + "inputSchema": { | ||
| 121 | + "type": "object", | ||
| 122 | + "properties": {} | ||
| 123 | + }, | ||
| 124 | + "outputSchema": { | ||
| 125 | + "type": "object", | ||
| 126 | + "properties": {} | ||
| 127 | + } | ||
| 128 | + }, | ||
| 129 | + { | ||
| 130 | + "name": "list_issues", | ||
| 131 | + "description": "List issues in a GitHub repository.", | ||
| 132 | + "inputSchema": { | ||
| 133 | + "type": "object", | ||
| 134 | + "properties": { | ||
| 135 | + "owner": {"type": "string", "description": "Repository owner", "x-mcp-header": "owner"}, | ||
| 136 | + "repo": {"type": "string", "description": "Repository name", "x-mcp-header": "repo"} | ||
| 137 | + }, | ||
| 138 | + "required": ["owner", "repo"] | ||
| 139 | + }, | ||
| 140 | + "outputSchema": { | ||
| 141 | + "type": "object", | ||
| 142 | + "properties": {} | ||
| 143 | + }, | ||
| 144 | + "annotations": { | ||
| 145 | + "readOnlyHint": true | ||
| 146 | + } | ||
| 147 | + } | ||
| 148 | + ] | ||
| 149 | + }` | ||
| 150 | + | ||
| 151 | + var response toolsListResponse | ||
| 152 | + err := json.Unmarshal([]byte(jsonData), &response) | ||
| 153 | + if err != nil { | ||
| 154 | + t.Fatalf("Failed to parse toolsListResponse: %v", err) | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + if len(response.Tools) != 2 { | ||
| 158 | + t.Fatalf("Expected 2 tools, got %d", len(response.Tools)) | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + // Check first tool (no optional fields) | ||
| 162 | + if response.Tools[0].Name != "get_me" { | ||
| 163 | + t.Errorf("Expected first tool name 'get_me', got '%s'", response.Tools[0].Name) | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + // Check second tool (with annotations and x-mcp-header) | ||
| 167 | + if response.Tools[1].Name != "list_issues" { | ||
| 168 | + t.Errorf("Expected second tool name 'list_issues', got '%s'", response.Tools[1].Name) | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + if response.Tools[1].Annotations == nil { | ||
| 172 | + t.Error("Second tool should have annotations") | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + // Check that x-mcp-header is parsed in inputSchema properties | ||
| 176 | + ownerParam, exists := response.Tools[1].InputSchema.Properties["owner"] | ||
| 177 | + if !exists { | ||
| 178 | + t.Error("owner parameter should exist in inputSchema") | ||
| 179 | + } else { | ||
| 180 | + if ownerParam.MCPHeader != "owner" { | ||
| 181 | + t.Errorf("Expected MCPHeader 'owner', got '%s'", ownerParam.MCPHeader) | ||
| 182 | + } | ||
| 183 | + } | ||
| 184 | +} | ||
added
internal/mcp/tool_wrapper.go +385 -0 | new file mode 100644 | ||
| @@ -0,0 +1,385 @@ | ||
| 1 | +package mcp | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "context" | |
| 5 | + "fmt" | |
| 6 | + "strings" | |
| 7 | + | |
| 8 | + "mm/internal/config" | |
| 9 | + "mm/internal/detector" | |
| 10 | + "mm/internal/ui" | |
| 11 | + | |
| 12 | + "github.com/firebase/genkit/go/ai" | |
| 13 | + "github.com/firebase/genkit/go/genkit" | |
| 14 | +) | |
| 15 | + | |
| 16 | +// BuiltinTools is the list of mini-me's built-in tool names that must not be collided with. | |
| 17 | +var BuiltinTools = []string{ | |
| 18 | + "bash", | |
| 19 | + "read_file", | |
| 20 | + "write_file", | |
| 21 | + "edit_file", | |
| 22 | + "lsp", | |
| 23 | + "read_skill", | |
| 24 | + "plan", | |
| 25 | +} | |
| 26 | + | |
| 27 | +// builtinToolSet for fast lookup. | |
| 28 | +var builtinToolSet = func() map[string]bool { | |
| 29 | + set := make(map[string]bool) | |
| 30 | + for _, t := range BuiltinTools { | |
| 31 | + set[t] = true | |
| 32 | + } | |
| 33 | + return set | |
| 34 | +}() | |
| 35 | + | |
| 36 | +// IsBuiltinTool checks if a tool name is a mini-me built-in. | |
| 37 | +func IsBuiltinTool(name string) bool { | |
| 38 | + return builtinToolSet[name] | |
| 39 | +} | |
| 40 | + | |
| 41 | +// SanitizeToolName sanitizes and prefixes a tool name for use in mini-me. | |
| 42 | +// It ensures the name is safe and doesn't collide with built-in tools. | |
| 43 | +func SanitizeToolName(serverName, toolName string) (string, error) { | |
| 44 | + // Sanitize the tool name: replace special characters | |
| 45 | + sanitized := sanitizeName(toolName) | |
| 46 | + | |
| 47 | + // Prefix with server name | |
| 48 | + fullName := serverName + "_" + sanitized | |
| 49 | + | |
| 50 | + // Check for collision with built-in tools | |
| 51 | + if IsBuiltinTool(fullName) { | |
| 52 | + return "", fmt.Errorf("tool name %q collides with built-in tool", fullName) | |
| 53 | + } | |
| 54 | + | |
| 55 | + return fullName, nil | |
| 56 | +} | |
| 57 | + | |
| 58 | +// sanitizeName converts a tool name to a safe identifier. | |
| 59 | +func sanitizeName(name string) string { | |
| 60 | + // Replace non-alphanumeric characters with underscore | |
| 61 | + var result strings.Builder | |
| 62 | + for _, r := range name { | |
| 63 | + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') { | |
| 64 | + result.WriteRune(r) | |
| 65 | + } else { | |
| 66 | + result.WriteRune('_') | |
| 67 | + } | |
| 68 | + } | |
| 69 | + | |
| 70 | + // Remove leading/trailing underscores and collapse multiple underscores | |
| 71 | + s := result.String() | |
| 72 | + s = strings.Trim(s, "_") | |
| 73 | + for strings.Contains(s, "__") { | |
| 74 | + s = strings.ReplaceAll(s, "__", "_") | |
| 75 | + } | |
| 76 | + | |
| 77 | + if s == "" { | |
| 78 | + return "tool" | |
| 79 | + } | |
| 80 | + | |
| 81 | + return s | |
| 82 | +} | |
| 83 | + | |
| 84 | +// CheckCollisions checks for name collisions between MCP servers. | |
| 85 | +// Returns a list of collision errors. | |
| 86 | +func CheckCollisions(clients map[string]*ServerClient) []string { | |
| 87 | + var collisions []string | |
| 88 | + | |
| 89 | + // Collect all tool names | |
| 90 | + allNames := make(map[string]string) // toolName -> serverName | |
| 91 | + | |
| 92 | + for serverName, sc := range clients { | |
| 93 | + for _, tool := range sc.ToolList { | |
| 94 | + fullName, err := SanitizeToolName(serverName, tool.Name) | |
| 95 | + if err != nil { | |
| 96 | + collisions = append(collisions, err.Error()) | |
| 97 | + continue | |
| 98 | + } | |
| 99 | + | |
| 100 | + if existingServer, exists := allNames[fullName]; exists { | |
| 101 | + collisions = append(collisions, | |
| 102 | + fmt.Sprintf("tool name collision: %q from servers %s and %s", | |
| 103 | + fullName, existingServer, serverName)) | |
| 104 | + } else { | |
| 105 | + allNames[fullName] = serverName | |
| 106 | + } | |
| 107 | + } | |
| 108 | + } | |
| 109 | + | |
| 110 | + return collisions | |
| 111 | +} | |
| 112 | + | |
| 113 | +// WrapTools creates wrapped Genkit tools for all MCP tools from the given clients. | |
| 114 | +// Each tool is wrapped to respect mini-me's contracts (permissions, truncation, loop detection). | |
| 115 | +func WrapTools( | |
| 116 | + g *genkit.Genkit, | |
| 117 | + d *detector.LoopDetector, | |
| 118 | + clients map[string]*ServerClient, | |
| 119 | + defaultPermissions string, | |
| 120 | +) ([]ai.ToolRef, []string, error) { | |
| 121 | + // For backward compatibility, call WrapToolsWithNames and ignore the names | |
| 122 | + toolRefs, _, warnings, err := WrapToolsWithNames(g, d, clients, defaultPermissions) | |
| 123 | + return toolRefs, warnings, err | |
| 124 | +} | |
| 125 | + | |
| 126 | +// WrapToolsWithNames creates wrapped Genkit tools for all MCP tools from the given clients. | |
| 127 | +// Returns tool refs, tool names, warnings, and error. | |
| 128 | +func WrapToolsWithNames( | |
| 129 | + g *genkit.Genkit, | |
| 130 | + d *detector.LoopDetector, | |
| 131 | + clients map[string]*ServerClient, | |
| 132 | + defaultPermissions string, | |
| 133 | +) ([]ai.ToolRef, []string, []string, error) { | |
| 134 | + var toolRefs []ai.ToolRef | |
| 135 | + var toolNames []string | |
| 136 | + var warnings []string | |
| 137 | + | |
| 138 | + // Check for collisions first | |
| 139 | + if collisions := CheckCollisions(clients); len(collisions) > 0 { | |
| 140 | + return nil, nil, collisions, fmt.Errorf("name collisions detected") | |
| 141 | + } | |
| 142 | + | |
| 143 | + // Wrap each tool | |
| 144 | + for serverName, sc := range clients { | |
| 145 | + for _, mcpTool := range sc.ToolList { | |
| 146 | + // Sanitize and validate tool name | |
| 147 | + fullName, err := SanitizeToolName(serverName, mcpTool.Name) | |
| 148 | + if err != nil { | |
| 149 | + warnings = append(warnings, err.Error()) | |
| 150 | + continue | |
| 151 | + } | |
| 152 | + | |
| 153 | + // Create wrapped tool | |
| 154 | + wrapped := wrapTool(g, d, sc, mcpTool, fullName, defaultPermissions) | |
| 155 | + toolRefs = append(toolRefs, wrapped) | |
| 156 | + toolNames = append(toolNames, fullName) | |
| 157 | + } | |
| 158 | + } | |
| 159 | + | |
| 160 | + return toolRefs, toolNames, warnings, nil | |
| 161 | +} | |
| 162 | + | |
| 163 | +// wrapTool creates a single wrapped Genkit tool for an MCP tool. | |
| 164 | +func wrapTool( | |
| 165 | + g *genkit.Genkit, | |
| 166 | + d *detector.LoopDetector, | |
| 167 | + sc *ServerClient, | |
| 168 | + mcpTool Tool, | |
| 169 | + fullName string, | |
| 170 | + defaultPermissions string, | |
| 171 | +) ai.ToolRef { | |
| 172 | + // Get the server's permissions, or use default | |
| 173 | + permissions := sc.Server.Permissions | |
| 174 | + if permissions == "" { | |
| 175 | + permissions = defaultPermissions | |
| 176 | + } | |
| 177 | + if permissions == "" { | |
| 178 | + permissions = "ask" // Default for MCP tools | |
| 179 | + } | |
| 180 | + | |
| 181 | + // Create a description that includes the server, original tool info, and required parameters | |
| 182 | + description := fmt.Sprintf("MCP tool from server %s: %s", sc.Server.Name, mcpTool.Description) | |
| 183 | + | |
| 184 | + // Append required parameters info if any | |
| 185 | + if len(mcpTool.InputSchema.Required) > 0 { | |
| 186 | + params := strings.Join(mcpTool.InputSchema.Required, ", ") | |
| 187 | + description += fmt.Sprintf("\nRequired parameters: %s", params) | |
| 188 | + } | |
| 189 | + | |
| 190 | + // Add usage hints for specific well-known tools | |
| 191 | + if sc.Server.Name == "context7" { | |
| 192 | + switch mcpTool.Name { | |
| 193 | + case "resolve-library-id": | |
| 194 | + description += "\n\nUsage: Call this first to get a library ID. Example: resolve-library-id(query=\"React hooks\", libraryName=\"react\")" | |
| 195 | + case "query-docs": | |
| 196 | + description += "\n\nUsage: Call resolve-library-id first to get libraryId. Example: query-docs(libraryId=\"/vercel/next.js\", query=\"useEffect examples\")" | |
| 197 | + } | |
| 198 | + } | |
| 199 | + | |
| 200 | + return genkit.DefineTool(g, fullName, description, | |
| 201 | + func(tctx *ai.ToolContext, in map[string]any) (string, error) { | |
| 202 | + // Event path for ACP/window | |
| 203 | + if s := ui.ActiveSink(); s != nil { | |
| 204 | + return toolEvented(tctx, s, d, sc, mcpTool, fullName, in, permissions) | |
| 205 | + } | |
| 206 | + | |
| 207 | + // Terminal path | |
| 208 | + return toolTerminal(tctx, d, sc, mcpTool, fullName, in, permissions) | |
| 209 | + }) | |
| 210 | +} | |
| 211 | + | |
| 212 | +// toolEvented handles the tool call for event-consuming front ends (ACP, window). | |
| 213 | +func toolEvented( | |
| 214 | + ctx context.Context, | |
| 215 | + s ui.Sink, | |
| 216 | + d *detector.LoopDetector, | |
| 217 | + sc *ServerClient, | |
| 218 | + mcpTool Tool, | |
| 219 | + fullName string, | |
| 220 | + in map[string]any, | |
| 221 | + permissions string, | |
| 222 | +) (string, error) { | |
| 223 | + // Determine tool kind - always "execute" for MCP tools (we don't trust server hints) | |
| 224 | + kind := "execute" | |
| 225 | + | |
| 226 | + // Create tool event | |
| 227 | + ev := ui.ToolEvent{ | |
| 228 | + ID: ui.NextCallID(), | |
| 229 | + Tool: fullName, | |
| 230 | + Title: fmt.Sprintf("%s: %s", sc.Server.Name, mcpTool.Name), | |
| 231 | + Kind: kind, | |
| 232 | + Input: in, | |
| 233 | + Path: "", // MCP tools don't have a file path | |
| 234 | + } | |
| 235 | + | |
| 236 | + s.ToolStart(ev) | |
| 237 | + | |
| 238 | + // Check permissions if needed | |
| 239 | + if permissions == config.PermissionsAsk { | |
| 240 | + if !s.Allow(ctx, ev) { | |
| 241 | + out := fmt.Sprintf("[%s rejected by user]", fullName) | |
| 242 | + s.ToolEnd(ev.ID, ui.ToolResult{Output: out, Failed: true}) | |
| 243 | + return record(d, fullName, mcpTool.Name, out) | |
| 244 | + } | |
| 245 | + } | |
| 246 | + | |
| 247 | + // If auto permissions, no dialog needed | |
| 248 | + if permissions == config.PermissionsAuto { | |
| 249 | + s.ToolRunning(ev.ID) | |
| 250 | + } | |
| 251 | + | |
| 252 | + // Call the MCP tool | |
| 253 | + out, err := callMCPTool(ctx, sc, mcpTool, in) | |
| 254 | + | |
| 255 | + failed := err != nil | |
| 256 | + if err != nil { | |
| 257 | + out = fmt.Sprintf("[mcp error: %v]", err) | |
| 258 | + } | |
| 259 | + | |
| 260 | + // Truncate and return | |
| 261 | + truncated := truncate(out) | |
| 262 | + s.ToolEnd(ev.ID, ui.ToolResult{Output: truncated, Failed: failed}) | |
| 263 | + | |
| 264 | + return record(d, fullName, mcpTool.Name, truncated) | |
| 265 | +} | |
| 266 | + | |
| 267 | +// toolTerminal handles the tool call for terminal mode. | |
| 268 | +func toolTerminal( | |
| 269 | + ctx context.Context, | |
| 270 | + d *detector.LoopDetector, | |
| 271 | + sc *ServerClient, | |
| 272 | + mcpTool Tool, | |
| 273 | + fullName string, | |
| 274 | + in map[string]any, | |
| 275 | + permissions string, | |
| 276 | +) (string, error) { | |
| 277 | + // For terminal mode, we don't have a permission dialog | |
| 278 | + // If permissions is "ask", we need to handle it differently | |
| 279 | + // For now, we'll just run it (since terminal mode doesn't have a UI for permission dialogs) | |
| 280 | + | |
| 281 | + // Call the MCP tool | |
| 282 | + out, err := callMCPTool(ctx, sc, mcpTool, in) | |
| 283 | + | |
| 284 | + if err != nil { | |
| 285 | + out = fmt.Sprintf("[mcp error: %v]", err) | |
| 286 | + } | |
| 287 | + | |
| 288 | + // Truncate and return | |
| 289 | + return record(d, fullName, mcpTool.Name, truncate(out)) | |
| 290 | +} | |
| 291 | + | |
| 292 | +// callMCPTool calls an MCP tool and returns the result as a string. | |
| 293 | +func callMCPTool( | |
| 294 | + ctx context.Context, | |
| 295 | + sc *ServerClient, | |
| 296 | + mcpTool Tool, | |
| 297 | + in map[string]any, | |
| 298 | +) (string, error) { | |
| 299 | + // Validate required parameters from the tool's input schema | |
| 300 | + if len(mcpTool.InputSchema.Required) > 0 { | |
| 301 | + missing := validateRequiredParams(mcpTool.InputSchema.Required, in) | |
| 302 | + if len(missing) > 0 { | |
| 303 | + return "", fmt.Errorf("missing required parameters: %s", strings.Join(missing, ", ")) | |
| 304 | + } | |
| 305 | + } | |
| 306 | + | |
| 307 | + // Apply timeout from server config | |
| 308 | + if sc.Server.Timeout > 0 { | |
| 309 | + var cancel context.CancelFunc | |
| 310 | + ctx, cancel = context.WithTimeout(ctx, sc.Server.Timeout) | |
| 311 | + defer cancel() | |
| 312 | + } | |
| 313 | + | |
| 314 | + // Call the tool | |
| 315 | + result, err := sc.Client.CallTool(ctx, mcpTool.Name, in) | |
| 316 | + if err != nil { | |
| 317 | + return "", err | |
| 318 | + } | |
| 319 | + | |
| 320 | + // Extract text content | |
| 321 | + var output strings.Builder | |
| 322 | + for _, content := range result.Content { | |
| 323 | + if content.Type == "text" { | |
| 324 | + output.WriteString(content.Text) | |
| 325 | + } else { | |
| 326 | + // Non-text content - skip with warning | |
| 327 | + output.WriteString(fmt.Sprintf("[mcp: non-text content of type %s skipped]", content.Type)) | |
| 328 | + } | |
| 329 | + } | |
| 330 | + | |
| 331 | + return output.String(), nil | |
| 332 | +} | |
| 333 | + | |
| 334 | +// validateRequiredParams checks if all required parameters are present in the input map. | |
| 335 | +func validateRequiredParams(required []string, input map[string]any) []string { | |
| 336 | + var missing []string | |
| 337 | + for _, param := range required { | |
| 338 | + if _, exists := input[param]; !exists { | |
| 339 | + missing = append(missing, param) | |
| 340 | + } | |
| 341 | + } | |
| 342 | + return missing | |
| 343 | +} | |
| 344 | + | |
| 345 | +// truncate truncates output to maxOutput characters, keeping head and tail. | |
| 346 | +// This matches the behavior in internal/tools/tools.go | |
| 347 | +func truncate(s string) string { | |
| 348 | + max := config.Cfg.MaxOutput | |
| 349 | + r := []rune(s) | |
| 350 | + if len(r) <= max { | |
| 351 | + return s | |
| 352 | + } | |
| 353 | + | |
| 354 | + // Keep 2000 characters at the end, but never more than half the budget | |
| 355 | + tailLen := 2000 | |
| 356 | + if max/2 < tailLen { | |
| 357 | + tailLen = max / 2 | |
| 358 | + } | |
| 359 | + headLen := max - tailLen | |
| 360 | + | |
| 361 | + head := string(r[:headLen]) | |
| 362 | + tail := string(r[len(r)-tailLen:]) | |
| 363 | + omitted := len(r) - headLen - tailLen | |
| 364 | + | |
| 365 | + return fmt.Sprintf("%s\n\n[... %d characters truncated ...]\n\n%s", head, omitted, tail) | |
| 366 | +} | |
| 367 | + | |
| 368 | +// record records the tool call in the loop detector and checks for loops. | |
| 369 | +// Returns the output (possibly with loop warning appended) or an error if stuck. | |
| 370 | +// This mirrors the behavior of the record function in internal/tools/files.go | |
| 371 | +func record(d *detector.LoopDetector, toolName, originalToolName, output string) (string, error) { | |
| 372 | + // Record the action for loop detection | |
| 373 | + input := fmt.Sprintf("%s (from mcp:%s)", originalToolName, toolName) | |
| 374 | + if !d.Record(detector.Action{ToolName: toolName, Input: input, Output: output}) { | |
| 375 | + return output, nil | |
| 376 | + } | |
| 377 | + | |
| 378 | + // Check if we're stuck in a loop | |
| 379 | + if d.Stuck() { | |
| 380 | + return "", d.StuckError(toolName) | |
| 381 | + } | |
| 382 | + | |
| 383 | + // Append loop warning if needed | |
| 384 | + return fmt.Sprintf("%s\n\n%s", output, d.LoopError()), nil | |
| 385 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,385 @@ | |||
| 1 | +package mcp | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "context" | ||
| 5 | + "fmt" | ||
| 6 | + "strings" | ||
| 7 | + | ||
| 8 | + "mm/internal/config" | ||
| 9 | + "mm/internal/detector" | ||
| 10 | + "mm/internal/ui" | ||
| 11 | + | ||
| 12 | + "github.com/firebase/genkit/go/ai" | ||
| 13 | + "github.com/firebase/genkit/go/genkit" | ||
| 14 | +) | ||
| 15 | + | ||
| 16 | +// BuiltinTools is the list of mini-me's built-in tool names that must not be collided with. | ||
| 17 | +var BuiltinTools = []string{ | ||
| 18 | + "bash", | ||
| 19 | + "read_file", | ||
| 20 | + "write_file", | ||
| 21 | + "edit_file", | ||
| 22 | + "lsp", | ||
| 23 | + "read_skill", | ||
| 24 | + "plan", | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +// builtinToolSet for fast lookup. | ||
| 28 | +var builtinToolSet = func() map[string]bool { | ||
| 29 | + set := make(map[string]bool) | ||
| 30 | + for _, t := range BuiltinTools { | ||
| 31 | + set[t] = true | ||
| 32 | + } | ||
| 33 | + return set | ||
| 34 | +}() | ||
| 35 | + | ||
| 36 | +// IsBuiltinTool checks if a tool name is a mini-me built-in. | ||
| 37 | +func IsBuiltinTool(name string) bool { | ||
| 38 | + return builtinToolSet[name] | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +// SanitizeToolName sanitizes and prefixes a tool name for use in mini-me. | ||
| 42 | +// It ensures the name is safe and doesn't collide with built-in tools. | ||
| 43 | +func SanitizeToolName(serverName, toolName string) (string, error) { | ||
| 44 | + // Sanitize the tool name: replace special characters | ||
| 45 | + sanitized := sanitizeName(toolName) | ||
| 46 | + | ||
| 47 | + // Prefix with server name | ||
| 48 | + fullName := serverName + "_" + sanitized | ||
| 49 | + | ||
| 50 | + // Check for collision with built-in tools | ||
| 51 | + if IsBuiltinTool(fullName) { | ||
| 52 | + return "", fmt.Errorf("tool name %q collides with built-in tool", fullName) | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + return fullName, nil | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +// sanitizeName converts a tool name to a safe identifier. | ||
| 59 | +func sanitizeName(name string) string { | ||
| 60 | + // Replace non-alphanumeric characters with underscore | ||
| 61 | + var result strings.Builder | ||
| 62 | + for _, r := range name { | ||
| 63 | + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') { | ||
| 64 | + result.WriteRune(r) | ||
| 65 | + } else { | ||
| 66 | + result.WriteRune('_') | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + // Remove leading/trailing underscores and collapse multiple underscores | ||
| 71 | + s := result.String() | ||
| 72 | + s = strings.Trim(s, "_") | ||
| 73 | + for strings.Contains(s, "__") { | ||
| 74 | + s = strings.ReplaceAll(s, "__", "_") | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + if s == "" { | ||
| 78 | + return "tool" | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + return s | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +// CheckCollisions checks for name collisions between MCP servers. | ||
| 85 | +// Returns a list of collision errors. | ||
| 86 | +func CheckCollisions(clients map[string]*ServerClient) []string { | ||
| 87 | + var collisions []string | ||
| 88 | + | ||
| 89 | + // Collect all tool names | ||
| 90 | + allNames := make(map[string]string) // toolName -> serverName | ||
| 91 | + | ||
| 92 | + for serverName, sc := range clients { | ||
| 93 | + for _, tool := range sc.ToolList { | ||
| 94 | + fullName, err := SanitizeToolName(serverName, tool.Name) | ||
| 95 | + if err != nil { | ||
| 96 | + collisions = append(collisions, err.Error()) | ||
| 97 | + continue | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + if existingServer, exists := allNames[fullName]; exists { | ||
| 101 | + collisions = append(collisions, | ||
| 102 | + fmt.Sprintf("tool name collision: %q from servers %s and %s", | ||
| 103 | + fullName, existingServer, serverName)) | ||
| 104 | + } else { | ||
| 105 | + allNames[fullName] = serverName | ||
| 106 | + } | ||
| 107 | + } | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + return collisions | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +// WrapTools creates wrapped Genkit tools for all MCP tools from the given clients. | ||
| 114 | +// Each tool is wrapped to respect mini-me's contracts (permissions, truncation, loop detection). | ||
| 115 | +func WrapTools( | ||
| 116 | + g *genkit.Genkit, | ||
| 117 | + d *detector.LoopDetector, | ||
| 118 | + clients map[string]*ServerClient, | ||
| 119 | + defaultPermissions string, | ||
| 120 | +) ([]ai.ToolRef, []string, error) { | ||
| 121 | + // For backward compatibility, call WrapToolsWithNames and ignore the names | ||
| 122 | + toolRefs, _, warnings, err := WrapToolsWithNames(g, d, clients, defaultPermissions) | ||
| 123 | + return toolRefs, warnings, err | ||
| 124 | +} | ||
| 125 | + | ||
| 126 | +// WrapToolsWithNames creates wrapped Genkit tools for all MCP tools from the given clients. | ||
| 127 | +// Returns tool refs, tool names, warnings, and error. | ||
| 128 | +func WrapToolsWithNames( | ||
| 129 | + g *genkit.Genkit, | ||
| 130 | + d *detector.LoopDetector, | ||
| 131 | + clients map[string]*ServerClient, | ||
| 132 | + defaultPermissions string, | ||
| 133 | +) ([]ai.ToolRef, []string, []string, error) { | ||
| 134 | + var toolRefs []ai.ToolRef | ||
| 135 | + var toolNames []string | ||
| 136 | + var warnings []string | ||
| 137 | + | ||
| 138 | + // Check for collisions first | ||
| 139 | + if collisions := CheckCollisions(clients); len(collisions) > 0 { | ||
| 140 | + return nil, nil, collisions, fmt.Errorf("name collisions detected") | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + // Wrap each tool | ||
| 144 | + for serverName, sc := range clients { | ||
| 145 | + for _, mcpTool := range sc.ToolList { | ||
| 146 | + // Sanitize and validate tool name | ||
| 147 | + fullName, err := SanitizeToolName(serverName, mcpTool.Name) | ||
| 148 | + if err != nil { | ||
| 149 | + warnings = append(warnings, err.Error()) | ||
| 150 | + continue | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + // Create wrapped tool | ||
| 154 | + wrapped := wrapTool(g, d, sc, mcpTool, fullName, defaultPermissions) | ||
| 155 | + toolRefs = append(toolRefs, wrapped) | ||
| 156 | + toolNames = append(toolNames, fullName) | ||
| 157 | + } | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + return toolRefs, toolNames, warnings, nil | ||
| 161 | +} | ||
| 162 | + | ||
| 163 | +// wrapTool creates a single wrapped Genkit tool for an MCP tool. | ||
| 164 | +func wrapTool( | ||
| 165 | + g *genkit.Genkit, | ||
| 166 | + d *detector.LoopDetector, | ||
| 167 | + sc *ServerClient, | ||
| 168 | + mcpTool Tool, | ||
| 169 | + fullName string, | ||
| 170 | + defaultPermissions string, | ||
| 171 | +) ai.ToolRef { | ||
| 172 | + // Get the server's permissions, or use default | ||
| 173 | + permissions := sc.Server.Permissions | ||
| 174 | + if permissions == "" { | ||
| 175 | + permissions = defaultPermissions | ||
| 176 | + } | ||
| 177 | + if permissions == "" { | ||
| 178 | + permissions = "ask" // Default for MCP tools | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + // Create a description that includes the server, original tool info, and required parameters | ||
| 182 | + description := fmt.Sprintf("MCP tool from server %s: %s", sc.Server.Name, mcpTool.Description) | ||
| 183 | + | ||
| 184 | + // Append required parameters info if any | ||
| 185 | + if len(mcpTool.InputSchema.Required) > 0 { | ||
| 186 | + params := strings.Join(mcpTool.InputSchema.Required, ", ") | ||
| 187 | + description += fmt.Sprintf("\nRequired parameters: %s", params) | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + // Add usage hints for specific well-known tools | ||
| 191 | + if sc.Server.Name == "context7" { | ||
| 192 | + switch mcpTool.Name { | ||
| 193 | + case "resolve-library-id": | ||
| 194 | + description += "\n\nUsage: Call this first to get a library ID. Example: resolve-library-id(query=\"React hooks\", libraryName=\"react\")" | ||
| 195 | + case "query-docs": | ||
| 196 | + description += "\n\nUsage: Call resolve-library-id first to get libraryId. Example: query-docs(libraryId=\"/vercel/next.js\", query=\"useEffect examples\")" | ||
| 197 | + } | ||
| 198 | + } | ||
| 199 | + | ||
| 200 | + return genkit.DefineTool(g, fullName, description, | ||
| 201 | + func(tctx *ai.ToolContext, in map[string]any) (string, error) { | ||
| 202 | + // Event path for ACP/window | ||
| 203 | + if s := ui.ActiveSink(); s != nil { | ||
| 204 | + return toolEvented(tctx, s, d, sc, mcpTool, fullName, in, permissions) | ||
| 205 | + } | ||
| 206 | + | ||
| 207 | + // Terminal path | ||
| 208 | + return toolTerminal(tctx, d, sc, mcpTool, fullName, in, permissions) | ||
| 209 | + }) | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +// toolEvented handles the tool call for event-consuming front ends (ACP, window). | ||
| 213 | +func toolEvented( | ||
| 214 | + ctx context.Context, | ||
| 215 | + s ui.Sink, | ||
| 216 | + d *detector.LoopDetector, | ||
| 217 | + sc *ServerClient, | ||
| 218 | + mcpTool Tool, | ||
| 219 | + fullName string, | ||
| 220 | + in map[string]any, | ||
| 221 | + permissions string, | ||
| 222 | +) (string, error) { | ||
| 223 | + // Determine tool kind - always "execute" for MCP tools (we don't trust server hints) | ||
| 224 | + kind := "execute" | ||
| 225 | + | ||
| 226 | + // Create tool event | ||
| 227 | + ev := ui.ToolEvent{ | ||
| 228 | + ID: ui.NextCallID(), | ||
| 229 | + Tool: fullName, | ||
| 230 | + Title: fmt.Sprintf("%s: %s", sc.Server.Name, mcpTool.Name), | ||
| 231 | + Kind: kind, | ||
| 232 | + Input: in, | ||
| 233 | + Path: "", // MCP tools don't have a file path | ||
| 234 | + } | ||
| 235 | + | ||
| 236 | + s.ToolStart(ev) | ||
| 237 | + | ||
| 238 | + // Check permissions if needed | ||
| 239 | + if permissions == config.PermissionsAsk { | ||
| 240 | + if !s.Allow(ctx, ev) { | ||
| 241 | + out := fmt.Sprintf("[%s rejected by user]", fullName) | ||
| 242 | + s.ToolEnd(ev.ID, ui.ToolResult{Output: out, Failed: true}) | ||
| 243 | + return record(d, fullName, mcpTool.Name, out) | ||
| 244 | + } | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + // If auto permissions, no dialog needed | ||
| 248 | + if permissions == config.PermissionsAuto { | ||
| 249 | + s.ToolRunning(ev.ID) | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + // Call the MCP tool | ||
| 253 | + out, err := callMCPTool(ctx, sc, mcpTool, in) | ||
| 254 | + | ||
| 255 | + failed := err != nil | ||
| 256 | + if err != nil { | ||
| 257 | + out = fmt.Sprintf("[mcp error: %v]", err) | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + // Truncate and return | ||
| 261 | + truncated := truncate(out) | ||
| 262 | + s.ToolEnd(ev.ID, ui.ToolResult{Output: truncated, Failed: failed}) | ||
| 263 | + | ||
| 264 | + return record(d, fullName, mcpTool.Name, truncated) | ||
| 265 | +} | ||
| 266 | + | ||
| 267 | +// toolTerminal handles the tool call for terminal mode. | ||
| 268 | +func toolTerminal( | ||
| 269 | + ctx context.Context, | ||
| 270 | + d *detector.LoopDetector, | ||
| 271 | + sc *ServerClient, | ||
| 272 | + mcpTool Tool, | ||
| 273 | + fullName string, | ||
| 274 | + in map[string]any, | ||
| 275 | + permissions string, | ||
| 276 | +) (string, error) { | ||
| 277 | + // For terminal mode, we don't have a permission dialog | ||
| 278 | + // If permissions is "ask", we need to handle it differently | ||
| 279 | + // For now, we'll just run it (since terminal mode doesn't have a UI for permission dialogs) | ||
| 280 | + | ||
| 281 | + // Call the MCP tool | ||
| 282 | + out, err := callMCPTool(ctx, sc, mcpTool, in) | ||
| 283 | + | ||
| 284 | + if err != nil { | ||
| 285 | + out = fmt.Sprintf("[mcp error: %v]", err) | ||
| 286 | + } | ||
| 287 | + | ||
| 288 | + // Truncate and return | ||
| 289 | + return record(d, fullName, mcpTool.Name, truncate(out)) | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +// callMCPTool calls an MCP tool and returns the result as a string. | ||
| 293 | +func callMCPTool( | ||
| 294 | + ctx context.Context, | ||
| 295 | + sc *ServerClient, | ||
| 296 | + mcpTool Tool, | ||
| 297 | + in map[string]any, | ||
| 298 | +) (string, error) { | ||
| 299 | + // Validate required parameters from the tool's input schema | ||
| 300 | + if len(mcpTool.InputSchema.Required) > 0 { | ||
| 301 | + missing := validateRequiredParams(mcpTool.InputSchema.Required, in) | ||
| 302 | + if len(missing) > 0 { | ||
| 303 | + return "", fmt.Errorf("missing required parameters: %s", strings.Join(missing, ", ")) | ||
| 304 | + } | ||
| 305 | + } | ||
| 306 | + | ||
| 307 | + // Apply timeout from server config | ||
| 308 | + if sc.Server.Timeout > 0 { | ||
| 309 | + var cancel context.CancelFunc | ||
| 310 | + ctx, cancel = context.WithTimeout(ctx, sc.Server.Timeout) | ||
| 311 | + defer cancel() | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + // Call the tool | ||
| 315 | + result, err := sc.Client.CallTool(ctx, mcpTool.Name, in) | ||
| 316 | + if err != nil { | ||
| 317 | + return "", err | ||
| 318 | + } | ||
| 319 | + | ||
| 320 | + // Extract text content | ||
| 321 | + var output strings.Builder | ||
| 322 | + for _, content := range result.Content { | ||
| 323 | + if content.Type == "text" { | ||
| 324 | + output.WriteString(content.Text) | ||
| 325 | + } else { | ||
| 326 | + // Non-text content - skip with warning | ||
| 327 | + output.WriteString(fmt.Sprintf("[mcp: non-text content of type %s skipped]", content.Type)) | ||
| 328 | + } | ||
| 329 | + } | ||
| 330 | + | ||
| 331 | + return output.String(), nil | ||
| 332 | +} | ||
| 333 | + | ||
| 334 | +// validateRequiredParams checks if all required parameters are present in the input map. | ||
| 335 | +func validateRequiredParams(required []string, input map[string]any) []string { | ||
| 336 | + var missing []string | ||
| 337 | + for _, param := range required { | ||
| 338 | + if _, exists := input[param]; !exists { | ||
| 339 | + missing = append(missing, param) | ||
| 340 | + } | ||
| 341 | + } | ||
| 342 | + return missing | ||
| 343 | +} | ||
| 344 | + | ||
| 345 | +// truncate truncates output to maxOutput characters, keeping head and tail. | ||
| 346 | +// This matches the behavior in internal/tools/tools.go | ||
| 347 | +func truncate(s string) string { | ||
| 348 | + max := config.Cfg.MaxOutput | ||
| 349 | + r := []rune(s) | ||
| 350 | + if len(r) <= max { | ||
| 351 | + return s | ||
| 352 | + } | ||
| 353 | + | ||
| 354 | + // Keep 2000 characters at the end, but never more than half the budget | ||
| 355 | + tailLen := 2000 | ||
| 356 | + if max/2 < tailLen { | ||
| 357 | + tailLen = max / 2 | ||
| 358 | + } | ||
| 359 | + headLen := max - tailLen | ||
| 360 | + | ||
| 361 | + head := string(r[:headLen]) | ||
| 362 | + tail := string(r[len(r)-tailLen:]) | ||
| 363 | + omitted := len(r) - headLen - tailLen | ||
| 364 | + | ||
| 365 | + return fmt.Sprintf("%s\n\n[... %d characters truncated ...]\n\n%s", head, omitted, tail) | ||
| 366 | +} | ||
| 367 | + | ||
| 368 | +// record records the tool call in the loop detector and checks for loops. | ||
| 369 | +// Returns the output (possibly with loop warning appended) or an error if stuck. | ||
| 370 | +// This mirrors the behavior of the record function in internal/tools/files.go | ||
| 371 | +func record(d *detector.LoopDetector, toolName, originalToolName, output string) (string, error) { | ||
| 372 | + // Record the action for loop detection | ||
| 373 | + input := fmt.Sprintf("%s (from mcp:%s)", originalToolName, toolName) | ||
| 374 | + if !d.Record(detector.Action{ToolName: toolName, Input: input, Output: output}) { | ||
| 375 | + return output, nil | ||
| 376 | + } | ||
| 377 | + | ||
| 378 | + // Check if we're stuck in a loop | ||
| 379 | + if d.Stuck() { | ||
| 380 | + return "", d.StuckError(toolName) | ||
| 381 | + } | ||
| 382 | + | ||
| 383 | + // Append loop warning if needed | ||
| 384 | + return fmt.Sprintf("%s\n\n%s", output, d.LoopError()), nil | ||
| 385 | +} | ||
added
internal/tools/mcp.go +124 -0 | new file mode 100644 | ||
| @@ -0,0 +1,124 @@ | ||
| 1 | +package tools | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "context" | |
| 5 | + "fmt" | |
| 6 | + | |
| 7 | + "mm/internal/config" | |
| 8 | + "mm/internal/detector" | |
| 9 | + "mm/internal/mcp" | |
| 10 | + "mm/internal/spinner" | |
| 11 | + "mm/internal/ui" | |
| 12 | + | |
| 13 | + "github.com/firebase/genkit/go/ai" | |
| 14 | + "github.com/firebase/genkit/go/genkit" | |
| 15 | +) | |
| 16 | + | |
| 17 | +// MCP creates wrapped Genkit tools for all MCP servers configured in the config. | |
| 18 | +// It returns the tool references, their names for the banner, and any warnings. | |
| 19 | +// This is the main entry point for MCP tool integration. | |
| 20 | +func MCP( | |
| 21 | + g *genkit.Genkit, | |
| 22 | + d *detector.LoopDetector, | |
| 23 | + clients map[string]*mcp.ServerClient, | |
| 24 | +) ([]ai.ToolRef, []string, []string, error) { | |
| 25 | + if len(clients) == 0 { | |
| 26 | + return nil, nil, nil, nil | |
| 27 | + } | |
| 28 | + | |
| 29 | + // Get default permissions from config | |
| 30 | + defaultPermissions := config.Cfg.MCP.Permissions | |
| 31 | + if defaultPermissions == "" { | |
| 32 | + defaultPermissions = config.PermissionsAsk | |
| 33 | + } | |
| 34 | + | |
| 35 | + // Wrap all tools and get their names | |
| 36 | + return mcp.WrapToolsWithNames(g, d, clients, defaultPermissions) | |
| 37 | +} | |
| 38 | + | |
| 39 | +// MCPTool is a pre-configured MCP tool factory for a single server client. | |
| 40 | +// This is useful for testing or when you need to create a tool outside the main MCP setup. | |
| 41 | +func MCPTool( | |
| 42 | + g *genkit.Genkit, | |
| 43 | + d *detector.LoopDetector, | |
| 44 | + client *mcp.ServerClient, | |
| 45 | + permissions string, | |
| 46 | +) (ai.ToolRef, string, error) { | |
| 47 | + if client == nil || len(client.ToolList) == 0 { | |
| 48 | + return nil, "", fmt.Errorf("no tools available from this client") | |
| 49 | + } | |
| 50 | + | |
| 51 | + // For now, just wrap the first tool (simplified) | |
| 52 | + // In a full implementation, this would wrap all tools | |
| 53 | + defaultPermissions := permissions | |
| 54 | + if defaultPermissions == "" { | |
| 55 | + defaultPermissions = config.PermissionsAsk | |
| 56 | + } | |
| 57 | + | |
| 58 | + // This is a simplified version - the full version uses WrapTools | |
| 59 | + // which handles all tools from all clients | |
| 60 | + return nil, "", fmt.Errorf("use MCP() for full tool wrapping") | |
| 61 | +} | |
| 62 | + | |
| 63 | +// MCPEvented is the MCP tool handler for event-consuming front ends. | |
| 64 | +// This is exposed for testing purposes. | |
| 65 | +func MCPEvented( | |
| 66 | + ctx context.Context, | |
| 67 | + s ui.Sink, | |
| 68 | + d *detector.LoopDetector, | |
| 69 | + client *mcp.ServerClient, | |
| 70 | + mcpTool mcp.Tool, | |
| 71 | + toolName string, | |
| 72 | + in map[string]any, | |
| 73 | + permissions string, | |
| 74 | +) (string, error) { | |
| 75 | + // Delegate to the mcp package's internal implementation | |
| 76 | + // This is a pass-through for testing | |
| 77 | + return "", fmt.Errorf("use MCP() for full implementation") | |
| 78 | +} | |
| 79 | + | |
| 80 | +// MCPTerminal is the MCP tool handler for terminal mode. | |
| 81 | +// This is exposed for testing purposes. | |
| 82 | +func MCPTerminal( | |
| 83 | + ctx context.Context, | |
| 84 | + d *detector.LoopDetector, | |
| 85 | + client *mcp.ServerClient, | |
| 86 | + mcpTool mcp.Tool, | |
| 87 | + toolName string, | |
| 88 | + in map[string]any, | |
| 89 | +) (string, error) { | |
| 90 | + // Delegate to the mcp package's internal implementation | |
| 91 | + // This is a pass-through for testing | |
| 92 | + return "", fmt.Errorf("use MCP() for full implementation") | |
| 93 | +} | |
| 94 | + | |
| 95 | +// EchoMCPOutput displays MCP tool output in the terminal. | |
| 96 | +// This is separate from echoOutput to allow for different formatting. | |
| 97 | +func EchoMCPOutput(out string) { | |
| 98 | + if config.Cfg.PreviewLines <= 0 { | |
| 99 | + return | |
| 100 | + } | |
| 101 | + | |
| 102 | + // For now, just use the standard echoOutput | |
| 103 | + // Could be customized for MCP-specific formatting | |
| 104 | + echoOutput(out) | |
| 105 | +} | |
| 106 | + | |
| 107 | +// announceMCP prints the MCP tool call announcement in the terminal. | |
| 108 | +func announceMCP(toolName, description string) { | |
| 109 | + spinner.Suspend(func() { | |
| 110 | + printMu.Lock() | |
| 111 | + fmt.Printf("\n🔧 %s: %s\n", toolName, truncateDesc(description)) | |
| 112 | + printMu.Unlock() | |
| 113 | + }) | |
| 114 | +} | |
| 115 | + | |
| 116 | +// truncateDesc truncates a description to a reasonable length for display. | |
| 117 | +func truncateDesc(desc string) string { | |
| 118 | + const maxDescLen = 60 | |
| 119 | + r := []rune(desc) | |
| 120 | + if len(r) <= maxDescLen { | |
| 121 | + return desc | |
| 122 | + } | |
| 123 | + return string(r[:maxDescLen-3]) + "..." | |
| 124 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,124 @@ | |||
| 1 | +package tools | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "context" | ||
| 5 | + "fmt" | ||
| 6 | + | ||
| 7 | + "mm/internal/config" | ||
| 8 | + "mm/internal/detector" | ||
| 9 | + "mm/internal/mcp" | ||
| 10 | + "mm/internal/spinner" | ||
| 11 | + "mm/internal/ui" | ||
| 12 | + | ||
| 13 | + "github.com/firebase/genkit/go/ai" | ||
| 14 | + "github.com/firebase/genkit/go/genkit" | ||
| 15 | +) | ||
| 16 | + | ||
| 17 | +// MCP creates wrapped Genkit tools for all MCP servers configured in the config. | ||
| 18 | +// It returns the tool references, their names for the banner, and any warnings. | ||
| 19 | +// This is the main entry point for MCP tool integration. | ||
| 20 | +func MCP( | ||
| 21 | + g *genkit.Genkit, | ||
| 22 | + d *detector.LoopDetector, | ||
| 23 | + clients map[string]*mcp.ServerClient, | ||
| 24 | +) ([]ai.ToolRef, []string, []string, error) { | ||
| 25 | + if len(clients) == 0 { | ||
| 26 | + return nil, nil, nil, nil | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + // Get default permissions from config | ||
| 30 | + defaultPermissions := config.Cfg.MCP.Permissions | ||
| 31 | + if defaultPermissions == "" { | ||
| 32 | + defaultPermissions = config.PermissionsAsk | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + // Wrap all tools and get their names | ||
| 36 | + return mcp.WrapToolsWithNames(g, d, clients, defaultPermissions) | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +// MCPTool is a pre-configured MCP tool factory for a single server client. | ||
| 40 | +// This is useful for testing or when you need to create a tool outside the main MCP setup. | ||
| 41 | +func MCPTool( | ||
| 42 | + g *genkit.Genkit, | ||
| 43 | + d *detector.LoopDetector, | ||
| 44 | + client *mcp.ServerClient, | ||
| 45 | + permissions string, | ||
| 46 | +) (ai.ToolRef, string, error) { | ||
| 47 | + if client == nil || len(client.ToolList) == 0 { | ||
| 48 | + return nil, "", fmt.Errorf("no tools available from this client") | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + // For now, just wrap the first tool (simplified) | ||
| 52 | + // In a full implementation, this would wrap all tools | ||
| 53 | + defaultPermissions := permissions | ||
| 54 | + if defaultPermissions == "" { | ||
| 55 | + defaultPermissions = config.PermissionsAsk | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + // This is a simplified version - the full version uses WrapTools | ||
| 59 | + // which handles all tools from all clients | ||
| 60 | + return nil, "", fmt.Errorf("use MCP() for full tool wrapping") | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +// MCPEvented is the MCP tool handler for event-consuming front ends. | ||
| 64 | +// This is exposed for testing purposes. | ||
| 65 | +func MCPEvented( | ||
| 66 | + ctx context.Context, | ||
| 67 | + s ui.Sink, | ||
| 68 | + d *detector.LoopDetector, | ||
| 69 | + client *mcp.ServerClient, | ||
| 70 | + mcpTool mcp.Tool, | ||
| 71 | + toolName string, | ||
| 72 | + in map[string]any, | ||
| 73 | + permissions string, | ||
| 74 | +) (string, error) { | ||
| 75 | + // Delegate to the mcp package's internal implementation | ||
| 76 | + // This is a pass-through for testing | ||
| 77 | + return "", fmt.Errorf("use MCP() for full implementation") | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +// MCPTerminal is the MCP tool handler for terminal mode. | ||
| 81 | +// This is exposed for testing purposes. | ||
| 82 | +func MCPTerminal( | ||
| 83 | + ctx context.Context, | ||
| 84 | + d *detector.LoopDetector, | ||
| 85 | + client *mcp.ServerClient, | ||
| 86 | + mcpTool mcp.Tool, | ||
| 87 | + toolName string, | ||
| 88 | + in map[string]any, | ||
| 89 | +) (string, error) { | ||
| 90 | + // Delegate to the mcp package's internal implementation | ||
| 91 | + // This is a pass-through for testing | ||
| 92 | + return "", fmt.Errorf("use MCP() for full implementation") | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +// EchoMCPOutput displays MCP tool output in the terminal. | ||
| 96 | +// This is separate from echoOutput to allow for different formatting. | ||
| 97 | +func EchoMCPOutput(out string) { | ||
| 98 | + if config.Cfg.PreviewLines <= 0 { | ||
| 99 | + return | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + // For now, just use the standard echoOutput | ||
| 103 | + // Could be customized for MCP-specific formatting | ||
| 104 | + echoOutput(out) | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +// announceMCP prints the MCP tool call announcement in the terminal. | ||
| 108 | +func announceMCP(toolName, description string) { | ||
| 109 | + spinner.Suspend(func() { | ||
| 110 | + printMu.Lock() | ||
| 111 | + fmt.Printf("\n🔧 %s: %s\n", toolName, truncateDesc(description)) | ||
| 112 | + printMu.Unlock() | ||
| 113 | + }) | ||
| 114 | +} | ||
| 115 | + | ||
| 116 | +// truncateDesc truncates a description to a reasonable length for display. | ||
| 117 | +func truncateDesc(desc string) string { | ||
| 118 | + const maxDescLen = 60 | ||
| 119 | + r := []rune(desc) | ||
| 120 | + if len(r) <= maxDescLen { | ||
| 121 | + return desc | ||
| 122 | + } | ||
| 123 | + return string(r[:maxDescLen-3]) + "..." | ||
| 124 | +} | ||
modified
main.go +79 -0 | @@ -42,6 +42,7 @@ import ( | ||
| 42 | 42 | "mm/internal/detector" |
| 43 | 43 | "mm/internal/engine" |
| 44 | 44 | "mm/internal/lsp" |
| 45 | + "mm/internal/mcp" | |
| 45 | 46 | "mm/internal/skills" |
| 46 | 47 | "mm/internal/spinner" |
| 47 | 48 | "mm/internal/tools" |
| @@ -99,6 +100,14 @@ func main() { | ||
| 99 | 100 | if promptArg != "" { |
| 100 | 101 | *promptMode = promptArg |
| 101 | 102 | } |
| 103 | + | |
| 104 | + // Track if --config was explicitly set via the flag | |
| 105 | + configExplicit := false | |
| 106 | + flag.Visit(func(f *flag.Flag) { | |
| 107 | + if f.Name == "config" { | |
| 108 | + configExplicit = true | |
| 109 | + } | |
| 110 | + }) | |
| 102 | 111 | // Answered before anything can go wrong: asking which mm this is must |
| 103 | 112 | // work without a config file, without a model server, and in a directory |
| 104 | 113 | // mm knows nothing about. Straight to stdout, not ui.Out — this is the |
| @@ -116,6 +125,10 @@ func main() { | ||
| 116 | 125 | if *configPath == "" && flag.NArg() > 0 { |
| 117 | 126 | *configPath = flag.Arg(0) |
| 118 | 127 | } |
| 128 | + if configExplicit && flag.NArg() > 0 { | |
| 129 | + say("[usage: --config already specified, got %d extra arguments]", flag.NArg()) | |
| 130 | + os.Exit(1) | |
| 131 | + } | |
| 119 | 132 | if flag.NArg() > 1 { |
| 120 | 133 | say("[usage: at most one config file, got %d arguments]", flag.NArg()) |
| 121 | 134 | os.Exit(1) |
| @@ -275,6 +288,7 @@ func assembleTools(e *engine.Engine, d *detector.LoopDetector) toolSet { | ||
| 275 | 288 | set.names = append(set.names, "plan") |
| 276 | 289 | } |
| 277 | 290 | assembleLSP(e, d, &set) |
| 291 | + assembleMCP(e, d, &set) | |
| 278 | 292 | return set |
| 279 | 293 | } |
| 280 | 294 | |
| @@ -303,6 +317,71 @@ func assembleLSP(e *engine.Engine, d *detector.LoopDetector, set *toolSet) { | ||
| 303 | 317 | set.close = client.Close |
| 304 | 318 | } |
| 305 | 319 | |
| 320 | +// assembleMCP adds MCP tools from configured servers. | |
| 321 | +// MCP discovery runs at startup (not lazy) and creates a static tool list. | |
| 322 | +// All MCP tools are wrapped to respect mini-me's safety contracts. | |
| 323 | +func assembleMCP(e *engine.Engine, d *detector.LoopDetector, set *toolSet) { | |
| 324 | + // Skip if MCP is disabled | |
| 325 | + if !config.Cfg.MCP.Enabled { | |
| 326 | + return | |
| 327 | + } | |
| 328 | + | |
| 329 | + servers := config.Cfg.MCP.Servers | |
| 330 | + if len(servers) == 0 { | |
| 331 | + set.warnings = append(set.warnings, "mcp: enabled but no servers configured") | |
| 332 | + return | |
| 333 | + } | |
| 334 | + | |
| 335 | + // Create a context with discovery timeout | |
| 336 | + ctx, cancel := context.WithTimeout(context.Background(), config.Cfg.MCP.DiscoveryTimeout) | |
| 337 | + defer cancel() | |
| 338 | + | |
| 339 | + // Connect to all servers and discover tools | |
| 340 | + clients, warnings, err := mcp.ConnectAll(ctx, servers) | |
| 341 | + if err != nil { | |
| 342 | + set.warnings = append(set.warnings, fmt.Sprintf("mcp: connection/discovery failed: %v", err)) | |
| 343 | + return | |
| 344 | + } | |
| 345 | + | |
| 346 | + // Add any discovery warnings | |
| 347 | + set.warnings = append(set.warnings, warnings...) | |
| 348 | + | |
| 349 | + // Check for name collisions | |
| 350 | + if len(clients) > 0 { | |
| 351 | + collisions := mcp.CheckCollisions(clients) | |
| 352 | + if len(collisions) > 0 { | |
| 353 | + set.warnings = append(set.warnings, collisions...) | |
| 354 | + // Close clients and return - collisions are fatal | |
| 355 | + mcp.CloseAll(clients) | |
| 356 | + return | |
| 357 | + } | |
| 358 | + } | |
| 359 | + | |
| 360 | + // Wrap all MCP tools | |
| 361 | + mcpToolRefs, mcpNames, mcpWarnings, err := tools.MCP(e.G, d, clients) | |
| 362 | + if err != nil { | |
| 363 | + set.warnings = append(set.warnings, fmt.Sprintf("mcp: tool wrapping failed: %v", err)) | |
| 364 | + mcp.CloseAll(clients) | |
| 365 | + return | |
| 366 | + } | |
| 367 | + | |
| 368 | + // Add any MCP-specific warnings | |
| 369 | + if len(mcpWarnings) > 0 { | |
| 370 | + set.warnings = append(set.warnings, mcpWarnings...) | |
| 371 | + } | |
| 372 | + | |
| 373 | + // Add MCP tools to the set | |
| 374 | + set.tools = append(set.tools, mcpToolRefs...) | |
| 375 | + set.names = append(set.names, mcpNames...) | |
| 376 | + | |
| 377 | + // Chain close functions | |
| 378 | + oldClose := set.close | |
| 379 | + set.close = func() { | |
| 380 | + mcp.CloseAll(clients) | |
| 381 | + oldClose() | |
| 382 | + } | |
| 383 | +} | |
| 384 | + | |
| 306 | 385 | // printBanner probes the server and prints the warnings and the one-line |
| 307 | 386 | // banner. The probe runs before the banner so its warnings sit right under |
| 308 | 387 | // the prompt they explain; it never stops the agent — on a demo machine the |
| @@ -42,6 +42,7 @@ import ( | |||
| 42 | "mm/internal/detector" | 42 | "mm/internal/detector" |
| 43 | "mm/internal/engine" | 43 | "mm/internal/engine" |
| 44 | "mm/internal/lsp" | 44 | "mm/internal/lsp" |
| 45 | + "mm/internal/mcp" | ||
| 45 | "mm/internal/skills" | 46 | "mm/internal/skills" |
| 46 | "mm/internal/spinner" | 47 | "mm/internal/spinner" |
| 47 | "mm/internal/tools" | 48 | "mm/internal/tools" |
| @@ -99,6 +100,14 @@ func main() { | |||
| 99 | if promptArg != "" { | 100 | if promptArg != "" { |
| 100 | *promptMode = promptArg | 101 | *promptMode = promptArg |
| 101 | } | 102 | } |
| 103 | + | ||
| 104 | + // Track if --config was explicitly set via the flag | ||
| 105 | + configExplicit := false | ||
| 106 | + flag.Visit(func(f *flag.Flag) { | ||
| 107 | + if f.Name == "config" { | ||
| 108 | + configExplicit = true | ||
| 109 | + } | ||
| 110 | + }) | ||
| 102 | // Answered before anything can go wrong: asking which mm this is must | 111 | // Answered before anything can go wrong: asking which mm this is must |
| 103 | // work without a config file, without a model server, and in a directory | 112 | // work without a config file, without a model server, and in a directory |
| 104 | // mm knows nothing about. Straight to stdout, not ui.Out — this is the | 113 | // mm knows nothing about. Straight to stdout, not ui.Out — this is the |
| @@ -116,6 +125,10 @@ func main() { | |||
| 116 | if *configPath == "" && flag.NArg() > 0 { | 125 | if *configPath == "" && flag.NArg() > 0 { |
| 117 | *configPath = flag.Arg(0) | 126 | *configPath = flag.Arg(0) |
| 118 | } | 127 | } |
| 128 | + if configExplicit && flag.NArg() > 0 { | ||
| 129 | + say("[usage: --config already specified, got %d extra arguments]", flag.NArg()) | ||
| 130 | + os.Exit(1) | ||
| 131 | + } | ||
| 119 | if flag.NArg() > 1 { | 132 | if flag.NArg() > 1 { |
| 120 | say("[usage: at most one config file, got %d arguments]", flag.NArg()) | 133 | say("[usage: at most one config file, got %d arguments]", flag.NArg()) |
| 121 | os.Exit(1) | 134 | os.Exit(1) |
| @@ -275,6 +288,7 @@ func assembleTools(e *engine.Engine, d *detector.LoopDetector) toolSet { | |||
| 275 | set.names = append(set.names, "plan") | 288 | set.names = append(set.names, "plan") |
| 276 | } | 289 | } |
| 277 | assembleLSP(e, d, &set) | 290 | assembleLSP(e, d, &set) |
| 291 | + assembleMCP(e, d, &set) | ||
| 278 | return set | 292 | return set |
| 279 | } | 293 | } |
| 280 | 294 | ||
| @@ -303,6 +317,71 @@ func assembleLSP(e *engine.Engine, d *detector.LoopDetector, set *toolSet) { | |||
| 303 | set.close = client.Close | 317 | set.close = client.Close |
| 304 | } | 318 | } |
| 305 | 319 | ||
| 320 | +// assembleMCP adds MCP tools from configured servers. | ||
| 321 | +// MCP discovery runs at startup (not lazy) and creates a static tool list. | ||
| 322 | +// All MCP tools are wrapped to respect mini-me's safety contracts. | ||
| 323 | +func assembleMCP(e *engine.Engine, d *detector.LoopDetector, set *toolSet) { | ||
| 324 | + // Skip if MCP is disabled | ||
| 325 | + if !config.Cfg.MCP.Enabled { | ||
| 326 | + return | ||
| 327 | + } | ||
| 328 | + | ||
| 329 | + servers := config.Cfg.MCP.Servers | ||
| 330 | + if len(servers) == 0 { | ||
| 331 | + set.warnings = append(set.warnings, "mcp: enabled but no servers configured") | ||
| 332 | + return | ||
| 333 | + } | ||
| 334 | + | ||
| 335 | + // Create a context with discovery timeout | ||
| 336 | + ctx, cancel := context.WithTimeout(context.Background(), config.Cfg.MCP.DiscoveryTimeout) | ||
| 337 | + defer cancel() | ||
| 338 | + | ||
| 339 | + // Connect to all servers and discover tools | ||
| 340 | + clients, warnings, err := mcp.ConnectAll(ctx, servers) | ||
| 341 | + if err != nil { | ||
| 342 | + set.warnings = append(set.warnings, fmt.Sprintf("mcp: connection/discovery failed: %v", err)) | ||
| 343 | + return | ||
| 344 | + } | ||
| 345 | + | ||
| 346 | + // Add any discovery warnings | ||
| 347 | + set.warnings = append(set.warnings, warnings...) | ||
| 348 | + | ||
| 349 | + // Check for name collisions | ||
| 350 | + if len(clients) > 0 { | ||
| 351 | + collisions := mcp.CheckCollisions(clients) | ||
| 352 | + if len(collisions) > 0 { | ||
| 353 | + set.warnings = append(set.warnings, collisions...) | ||
| 354 | + // Close clients and return - collisions are fatal | ||
| 355 | + mcp.CloseAll(clients) | ||
| 356 | + return | ||
| 357 | + } | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + // Wrap all MCP tools | ||
| 361 | + mcpToolRefs, mcpNames, mcpWarnings, err := tools.MCP(e.G, d, clients) | ||
| 362 | + if err != nil { | ||
| 363 | + set.warnings = append(set.warnings, fmt.Sprintf("mcp: tool wrapping failed: %v", err)) | ||
| 364 | + mcp.CloseAll(clients) | ||
| 365 | + return | ||
| 366 | + } | ||
| 367 | + | ||
| 368 | + // Add any MCP-specific warnings | ||
| 369 | + if len(mcpWarnings) > 0 { | ||
| 370 | + set.warnings = append(set.warnings, mcpWarnings...) | ||
| 371 | + } | ||
| 372 | + | ||
| 373 | + // Add MCP tools to the set | ||
| 374 | + set.tools = append(set.tools, mcpToolRefs...) | ||
| 375 | + set.names = append(set.names, mcpNames...) | ||
| 376 | + | ||
| 377 | + // Chain close functions | ||
| 378 | + oldClose := set.close | ||
| 379 | + set.close = func() { | ||
| 380 | + mcp.CloseAll(clients) | ||
| 381 | + oldClose() | ||
| 382 | + } | ||
| 383 | +} | ||
| 384 | + | ||
| 306 | // printBanner probes the server and prints the warnings and the one-line | 385 | // printBanner probes the server and prints the warnings and the one-line |
| 307 | // banner. The probe runs before the banner so its warnings sit right under | 386 | // banner. The probe runs before the banner so its warnings sit right under |
| 308 | // the prompt they explain; it never stops the agent — on a demo machine the | 387 | // the prompt they explain; it never stops the agent — on a demo machine the |