Skip to content

Commit 6bc6d93

Browse files
authored
Merge branch 'main' into add-issue-dependencies
2 parents 772ac52 + 2bd162a commit 6bc6d93

30 files changed

Lines changed: 1810 additions & 127 deletions

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ Alternatively, to manually configure VS Code, choose the appropriate JSON block
8686
- **[Claude Applications](/docs/installation-guides/install-claude.md)** - Installation guide for Claude Desktop and Claude Code CLI
8787
- **[Codex](/docs/installation-guides/install-codex.md)** - Installation guide for OpenAI Codex
8888
- **[Cursor](/docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE
89+
- **[OpenCode](/docs/installation-guides/install-opencode.md)** - Installation guide for the OpenCode terminal agent
8990
- **[Windsurf](/docs/installation-guides/install-windsurf.md)** - Installation guide for Windsurf IDE
91+
- **[Zed](/docs/installation-guides/install-zed.md)** - Installation guide for Zed editor
9092
- **[Rovo Dev CLI](/docs/installation-guides/install-rovo-dev-cli.md)** - Installation guide for Rovo Dev CLI
9193

9294
> **Note:** Each MCP host application needs to configure a GitHub App or OAuth App to support remote access via OAuth. Any host application that supports remote MCP servers should support the remote GitHub server with PAT authentication. Configuration details and support levels vary by host. Make sure to refer to the host application's documentation for more info.
@@ -356,7 +358,9 @@ For other MCP host applications, please refer to our installation guides:
356358
- **[Claude Code & Claude Desktop](docs/installation-guides/install-claude.md)** - Installation guide for Claude Code and Claude Desktop
357359
- **[Cursor](docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE
358360
- **[Google Gemini CLI](docs/installation-guides/install-gemini-cli.md)** - Installation guide for Google Gemini CLI
361+
- **[OpenCode](docs/installation-guides/install-opencode.md)** - Installation guide for the OpenCode terminal agent
359362
- **[Windsurf](docs/installation-guides/install-windsurf.md)** - Installation guide for Windsurf IDE
363+
- **[Zed](docs/installation-guides/install-zed.md)** - Installation guide for Zed editor
360364

361365
For a complete overview of all installation options, see our **[Installation Guides Index](docs/installation-guides)**.
362366

@@ -1052,22 +1056,26 @@ The following sets of tools are available:
10521056
- `project_number`: The project's number. Required for 'list_project_fields', 'list_project_items', and 'list_project_status_updates' methods. (number, optional)
10531057
- `query`: Filter/query string. For list_projects: filter by title text and state (e.g. "roadmap is:open"). For list_project_items: advanced filtering using GitHub's project filtering syntax. (string, optional)
10541058

1055-
- **projects_write** - Modify GitHub Project items
1059+
- **projects_write** - Manage GitHub Projects
10561060
- **Required OAuth Scopes**: `project`
10571061
- `body`: The body of the status update (markdown). Used for 'create_project_status_update' method. (string, optional)
1062+
- `field_name`: The name of the iteration field (e.g. 'Sprint'). Required for 'create_iteration_field' method. (string, optional)
10581063
- `issue_number`: The issue number (use when item_type is 'issue' for 'add_project_item' method). Provide either issue_number or pull_request_number. (number, optional)
10591064
- `item_id`: The project item ID. Required for 'update_project_item' and 'delete_project_item' methods. (number, optional)
10601065
- `item_owner`: The owner (user or organization) of the repository containing the issue or pull request. Required for 'add_project_item' method. (string, optional)
10611066
- `item_repo`: The name of the repository containing the issue or pull request. Required for 'add_project_item' method. (string, optional)
10621067
- `item_type`: The item's type, either issue or pull_request. Required for 'add_project_item' method. (string, optional)
1068+
- `iteration_duration`: Duration in days for iterations of the field (e.g. 7 for weekly, 14 for bi-weekly). Required for 'create_iteration_field' method. (number, optional)
1069+
- `iterations`: Custom iterations for 'create_iteration_field' method. Only set this when you need iterations with varying durations, breaks between them, or specific titles. Otherwise omit it: GitHub auto-creates three iterations of 'iteration_duration' days starting on 'start_date', which is the right choice for most cases. (object[], optional)
10631070
- `method`: The method to execute (string, required)
10641071
- `owner`: The project owner (user or organization login). The name is not case sensitive. (string, required)
1065-
- `owner_type`: Owner type (user or org). If not provided, will be automatically detected. (string, optional)
1066-
- `project_number`: The project's number. (number, required)
1072+
- `owner_type`: Owner type (user or org). Required for 'create_project' method. If not provided for other methods, will be automatically detected. (string, optional)
1073+
- `project_number`: The project's number. Required for all methods except 'create_project'. (number, optional)
10671074
- `pull_request_number`: The pull request number (use when item_type is 'pull_request' for 'add_project_item' method). Provide either issue_number or pull_request_number. (number, optional)
1068-
- `start_date`: The start date of the status update in YYYY-MM-DD format. Used for 'create_project_status_update' method. (string, optional)
1075+
- `start_date`: Start date in YYYY-MM-DD format. Used for 'create_project_status_update' and 'create_iteration_field' methods. (string, optional)
10691076
- `status`: The status of the project. Used for 'create_project_status_update' method. (string, optional)
10701077
- `target_date`: The target date of the status update in YYYY-MM-DD format. Used for 'create_project_status_update' method. (string, optional)
1078+
- `title`: The project title. Required for 'create_project' method. (string, optional)
10711079
- `updated_field`: Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {"id": 123456, "value": "New Value"}. Required for 'update_project_item' method. (object, optional)
10721080

10731081
</details>
@@ -1179,7 +1187,7 @@ The following sets of tools are available:
11791187
- `owner`: Repository owner (string, required)
11801188
- `pullNumber`: Pull request number to update (number, required)
11811189
- `repo`: Repository name (string, required)
1182-
- `reviewers`: GitHub usernames to request reviews from (string[], optional)
1190+
- `reviewers`: GitHub usernames or ORG/team-slug team reviewers to request reviews from (string[], optional)
11831191
- `state`: New state (string, optional)
11841192
- `title`: New title (string, optional)
11851193

cmd/github-mcp-server/generate_docs.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,11 @@ func generateRemoteToolsetsDoc() string {
365365
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")
366366
buf.WriteString("| ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- |\n")
367367

368-
// Add "all" toolset first (special case)
369-
allIcon := octiconImg("apps", "../")
370-
fmt.Fprintf(&buf, "| %s<br>`all` | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2F%%22%%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2Freadonly%%22%%7D) |\n", allIcon)
368+
// Add "default" and "all" meta toolsets first (special cases). The base
369+
// URL serves the default toolset; /x/all enables every toolset at once.
370+
metaIcon := octiconImg("apps", "../")
371+
fmt.Fprintf(&buf, "| %s<br>`default` | Default toolset | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2F%%22%%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2Freadonly%%22%%7D) |\n", metaIcon)
372+
fmt.Fprintf(&buf, "| %s<br>`all` | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/x/all | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-all&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2Fx%%2Fall%%22%%7D) | [read-only](https://api.githubcopilot.com/mcp/x/all/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-all&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2Fx%%2Fall%%2Freadonly%%22%%7D) |\n", metaIcon)
371373

372374
// AvailableToolsets() returns toolsets that have tools, sorted by ID
373375
// Exclude context (handled separately)

cmd/mcpcurl/main.go

Lines changed: 104 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package main
22

33
import (
4-
"bytes"
4+
"bufio"
55
"crypto/rand"
66
"encoding/json"
77
"fmt"
@@ -376,8 +376,8 @@ func buildJSONRPCRequest(method, toolName string, arguments map[string]any) (str
376376
return string(jsonData), nil
377377
}
378378

379-
// executeServerCommand runs the specified command, sends the JSON request to stdin,
380-
// and returns the response from stdout
379+
// executeServerCommand runs the specified command, performs the MCP initialization
380+
// handshake, sends the JSON request to stdin, and returns the response from stdout.
381381
func executeServerCommand(cmdStr, jsonRequest string) (string, error) {
382382
// Split the command string into command and arguments
383383
cmdParts := strings.Fields(cmdStr)
@@ -393,28 +393,119 @@ func executeServerCommand(cmdStr, jsonRequest string) (string, error) {
393393
return "", fmt.Errorf("failed to create stdin pipe: %w", err)
394394
}
395395

396-
// Setup stdout and stderr pipes
397-
var stdout, stderr bytes.Buffer
398-
cmd.Stdout = &stdout
396+
// Setup stdout pipe for line-by-line reading
397+
stdoutPipe, err := cmd.StdoutPipe()
398+
if err != nil {
399+
return "", fmt.Errorf("failed to create stdout pipe: %w", err)
400+
}
401+
402+
// Stderr still uses a buffer
403+
var stderr strings.Builder
399404
cmd.Stderr = &stderr
400405

401406
// Start the command
402407
if err := cmd.Start(); err != nil {
403408
return "", fmt.Errorf("failed to start command: %w", err)
404409
}
405410

406-
// Write the JSON request to stdin
411+
// Ensure the child process is cleaned up on every return path.
412+
// stdin must be closed before Wait so the server sees EOF and exits;
413+
// its non-zero exit status on EOF is expected, so we ignore the error.
414+
defer func() {
415+
_ = stdin.Close()
416+
_ = cmd.Wait()
417+
}()
418+
419+
// Use a scanner with a large buffer for reading JSON-RPC responses
420+
scanner := bufio.NewScanner(stdoutPipe)
421+
scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024) // 1MB max line size
422+
423+
// Step 1: Send MCP initialize request
424+
initReq, err := buildInitializeRequest()
425+
if err != nil {
426+
return "", fmt.Errorf("failed to build initialize request: %w", err)
427+
}
428+
if _, err := io.WriteString(stdin, initReq+"\n"); err != nil {
429+
return "", fmt.Errorf("failed to write initialize request: %w", err)
430+
}
431+
432+
// Step 2: Read initialize response (skip any server notifications)
433+
if _, err := readJSONRPCResponse(scanner); err != nil {
434+
return "", fmt.Errorf("failed to read initialize response: %w, stderr: %s", err, stderr.String())
435+
}
436+
437+
// Step 3: Send initialized notification
438+
if _, err := io.WriteString(stdin, buildInitializedNotification()+"\n"); err != nil {
439+
return "", fmt.Errorf("failed to write initialized notification: %w", err)
440+
}
441+
442+
// Step 4: Send the actual request
407443
if _, err := io.WriteString(stdin, jsonRequest+"\n"); err != nil {
408-
return "", fmt.Errorf("failed to write to stdin: %w", err)
444+
return "", fmt.Errorf("failed to write request: %w", err)
409445
}
410-
_ = stdin.Close()
411446

412-
// Wait for the command to complete
413-
if err := cmd.Wait(); err != nil {
414-
return "", fmt.Errorf("command failed: %w, stderr: %s", err, stderr.String())
447+
// Step 5: Read the actual response (skip any server notifications)
448+
response, err := readJSONRPCResponse(scanner)
449+
if err != nil {
450+
return "", fmt.Errorf("failed to read response: %w, stderr: %s", err, stderr.String())
415451
}
416452

417-
return stdout.String(), nil
453+
return response, nil
454+
}
455+
456+
// buildInitializeRequest creates the MCP initialize handshake request.
457+
func buildInitializeRequest() (string, error) {
458+
id, err := rand.Int(rand.Reader, big.NewInt(10000))
459+
if err != nil {
460+
return "", fmt.Errorf("failed to generate random ID: %w", err)
461+
}
462+
msg := map[string]any{
463+
"jsonrpc": "2.0",
464+
"id": int(id.Int64()),
465+
"method": "initialize",
466+
"params": map[string]any{
467+
"protocolVersion": "2024-11-05",
468+
"capabilities": map[string]any{},
469+
"clientInfo": map[string]any{
470+
"name": "mcpcurl",
471+
"version": "0.1.0",
472+
},
473+
},
474+
}
475+
data, err := json.Marshal(msg)
476+
if err != nil {
477+
return "", fmt.Errorf("failed to marshal initialize request: %w", err)
478+
}
479+
return string(data), nil
480+
}
481+
482+
// buildInitializedNotification creates the MCP initialized notification.
483+
func buildInitializedNotification() string {
484+
return `{"jsonrpc":"2.0","method":"notifications/initialized"}`
485+
}
486+
487+
// readJSONRPCResponse reads lines from the scanner, skipping server-initiated
488+
// notifications (messages without an "id" field), and returns the first response.
489+
func readJSONRPCResponse(scanner *bufio.Scanner) (string, error) {
490+
for scanner.Scan() {
491+
line := scanner.Text()
492+
// JSON-RPC responses have an "id" field; notifications do not.
493+
var msg map[string]json.RawMessage
494+
if err := json.Unmarshal([]byte(line), &msg); err != nil {
495+
return "", fmt.Errorf("failed to parse JSON-RPC message: %w", err)
496+
}
497+
if _, hasID := msg["id"]; hasID {
498+
if errField, hasErr := msg["error"]; hasErr {
499+
return "", fmt.Errorf("server returned error: %s", string(errField))
500+
}
501+
return line, nil
502+
}
503+
// No "id" — this is a notification, skip it
504+
}
505+
if err := scanner.Err(); err != nil {
506+
return "", err
507+
}
508+
return "", fmt.Errorf("unexpected end of output")
418509
}
419510

420511
func printResponse(response string, prettyPrint bool) error {

0 commit comments

Comments
 (0)