Skip to content

Commit 57701ff

Browse files
authored
Merge branch 'main' into kerobbi/get_tag_handle_lightweight_tags
2 parents 5a53046 + f48e82a commit 57701ff

16 files changed

Lines changed: 398 additions & 48 deletions

docs/installation-guides/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This directory contains detailed installation instructions for the GitHub MCP Se
1313
- **[OpenAI Codex](install-codex.md)** - Installation guide for OpenAI Codex
1414
- **[Roo Code](install-roo-code.md)** - Installation guide for Roo Code
1515
- **[Windsurf](install-windsurf.md)** - Installation guide for Windsurf IDE
16+
- **[Xcode (Codex & Claude Agent)](install-xcode.md)** - Installation guide for Codex and Claude Agent within Xcode
1617

1718
## Support by Host Application
1819

@@ -32,6 +33,8 @@ This directory contains detailed installation instructions for the GitHub MCP Se
3233
| Windsurf || ✅ PAT + ❌ No OAuth | Docker or Go build, GitHub PAT | Easy |
3334
| Copilot in Xcode || ✅ Full (OAuth + PAT) | Local: Docker or Go build, GitHub PAT<br>Remote: Copilot for Xcode 0.41.0+ | Easy |
3435
| Copilot in Eclipse || ✅ Full (OAuth + PAT) | Local: Docker or Go build, GitHub PAT<br>Remote: Eclipse Plug-in for Copilot 0.10.0+ | Easy |
36+
| Xcode (Codex) || ✅ PAT + ❌ No OAuth | Local: Docker (full path required), GitHub PAT<br>Remote: GitHub PAT via `GITHUB_PAT_TOKEN` env var (`bearer_token_env_var`) | Easy |
37+
| Xcode (Claude Agent) || ✅ PAT + ❌ No OAuth | Local: Docker (full path required), GitHub PAT<br>Remote: GitHub PAT | Easy |
3538

3639
**Legend:**
3740
- ✅ = Fully supported

docs/installation-guides/install-claude.md

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,75 @@ Add this codeblock to your `claude_desktop_config.json`:
164164

165165
---
166166

167-
## Troubleshooting
167+
## Xcode (Claude Agent)
168+
169+
Xcode's Claude Agent uses the same `.claude.json` configuration format as the Claude Code CLI, but reads it from an Xcode-specific directory rather than the global config location.
170+
171+
### Configuration File Location
172+
173+
```
174+
~/Library/Developer/Xcode/CodingAssistant/ClaudeAgentConfig/.claude.json
175+
```
176+
177+
> Configurations placed here only affect Claude Agent when launched from Xcode. See [Apple's documentation](https://developer.apple.com/documentation/xcode/setting-up-coding-intelligence#Customize-the-Claude-Agent-and-Codex-environments) for more details.
178+
179+
### Remote Server Setup (Recommended)
180+
181+
Run the following command in Terminal to add the remote GitHub MCP server:
182+
183+
```bash
184+
claude mcp add-json github '{"type":"http","url":"https://api.githubcopilot.com/mcp/","headers":{"Authorization":"Bearer YOUR_GITHUB_PAT"}}' --config ~/Library/Developer/Xcode/CodingAssistant/ClaudeAgentConfig/.claude.json
185+
```
186+
187+
Or open the file in a text editor and add the `mcpServers` block manually:
188+
189+
```json
190+
{
191+
"mcpServers": {
192+
"github": {
193+
"type": "http",
194+
"url": "https://api.githubcopilot.com/mcp/",
195+
"headers": {
196+
"Authorization": "Bearer YOUR_GITHUB_PAT"
197+
}
198+
}
199+
}
200+
}
201+
```
202+
203+
### Local Server Setup (Docker)
204+
205+
> **macOS note**: Xcode runs with a minimal `PATH` that typically excludes `/usr/local/bin` (Intel) and `/opt/homebrew/bin` (Apple Silicon). Use the full path to `docker` to ensure it can be found. Run `which docker` in Terminal to find the correct path on your system.
206+
207+
```json
208+
{
209+
"mcpServers": {
210+
"github": {
211+
"command": "/usr/local/bin/docker",
212+
"args": [
213+
"run",
214+
"-i",
215+
"--rm",
216+
"-e",
217+
"GITHUB_PERSONAL_ACCESS_TOKEN",
218+
"ghcr.io/github/github-mcp-server"
219+
],
220+
"env": {
221+
"GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_PAT"
222+
}
223+
}
224+
}
225+
}
226+
```
227+
228+
### Setup Steps
229+
1. Create or open `~/Library/Developer/Xcode/CodingAssistant/ClaudeAgentConfig/.claude.json`
230+
2. Add the configuration block above
231+
3. Replace `YOUR_GITHUB_PAT` with your actual token
232+
4. Restart Xcode
233+
234+
---
235+
168236

169237
**Authentication Failed:**
170238
- Verify PAT has `repo` scope
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Install GitHub MCP Server in Xcode
2+
3+
Xcode currently supports two built-in coding agents: **Codex** (powered by OpenAI) and **Claude Agent** (powered by Anthropic). Follow the standard installation guide for each agent, with one important difference: Xcode uses its own isolated configuration directories for each agent, separate from your global config.
4+
5+
> Configurations placed in these directories only affect agents when launched from Xcode. See [Apple's documentation](https://developer.apple.com/documentation/xcode/setting-up-coding-intelligence#Customize-the-Claude-Agent-and-Codex-environments) for more details.
6+
7+
## Configuration Directories
8+
9+
| Agent | Configuration Directory |
10+
|-------|------------------------|
11+
| Codex | `~/Library/Developer/Xcode/CodingAssistant/codex/` |
12+
| Claude Agent | `~/Library/Developer/Xcode/CodingAssistant/ClaudeAgentConfig/` |
13+
14+
Place your MCP server configuration in the relevant directory above rather than the default location used by the standalone CLI.
15+
16+
## Setup Guides
17+
18+
- **[Codex](install-codex.md)** — configure `config.toml` inside `~/Library/Developer/Xcode/CodingAssistant/codex/`
19+
- **[Claude Agent](install-claude.md#xcode-claude-agent)** — configure `.claude.json` inside `~/Library/Developer/Xcode/CodingAssistant/ClaudeAgentConfig/`
20+
21+
## macOS Path Note
22+
23+
Xcode runs with a minimal `PATH` that typically excludes common binary locations. If you are using a local STDIO server (e.g. Docker or a pre-built binary), use the **full path** to the command in your config. Run `which docker` (or `which github-mcp-server`) in Terminal to find the correct path on your system. Common locations:
24+
25+
| Installation | Typical path |
26+
|---|---|
27+
| Docker (Intel Mac) | `/usr/local/bin/docker` |
28+
| Docker (Apple Silicon) | `/usr/local/bin/docker` |
29+
| Homebrew (Intel Mac) | `/usr/local/bin/` |
30+
| Homebrew (Apple Silicon) | `/opt/homebrew/bin/` |
31+
32+
## Troubleshooting
33+
34+
| Issue | Possible Cause | Fix |
35+
|-------|----------------|-----|
36+
| Tools not loading | Config placed in wrong directory | Ensure config is in the Xcode-specific path above, not `~/.codex/` or `~/.claude.json` |
37+
| Command not found (STDIO) | Xcode's PATH excludes binary location | Use the full path (e.g. `/usr/local/bin/docker` or `/opt/homebrew/bin/docker`); run `which docker` in Terminal to confirm |
38+
| Docker not found | Docker not running | Start Docker Desktop and restart Xcode |
39+
| Authentication failed | Invalid or expired PAT | Regenerate PAT and update config |
40+
41+
## References
42+
43+
- [Apple Developer Documentation — Setting up coding intelligence](https://developer.apple.com/documentation/xcode/setting-up-coding-intelligence#Customize-the-Claude-Agent-and-Codex-environments)
44+
- [Codex MCP documentation](https://developers.openai.com/codex/mcp)
45+
- Main project README: [Advanced configuration options](../../README.md)

pkg/github/__toolsnaps__/actions_run_trigger.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"properties": {
99
"inputs": {
1010
"description": "Inputs the workflow accepts. Only used for 'run_workflow' method.",
11+
"properties": {},
1112
"type": "object"
1213
},
1314
"method": {

pkg/github/actions.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo
544544
"inputs": {
545545
Type: "object",
546546
Description: "Inputs the workflow accepts. Only used for 'run_workflow' method.",
547+
Properties: map[string]*jsonschema.Schema{},
547548
},
548549
"run_id": {
549550
Type: "number",
@@ -574,11 +575,9 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo
574575
runID, _ := OptionalIntParam(args, "run_id")
575576

576577
// Get optional inputs parameter
577-
var inputs map[string]any
578-
if requestInputs, ok := args["inputs"]; ok {
579-
if inputsMap, ok := requestInputs.(map[string]any); ok {
580-
inputs = inputsMap
581-
}
578+
inputs, err := OptionalParam[map[string]any](args, "inputs")
579+
if err != nil {
580+
return utils.NewToolResultError(err.Error()), nil, nil
582581
}
583582

584583
// Validate required parameters based on action type

pkg/github/actions_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,37 @@ func Test_ActionsRunTrigger_RunWorkflow(t *testing.T) {
377377
expectError: true,
378378
expectedErrMsg: "ref is required for run_workflow action",
379379
},
380+
{
381+
name: "successful workflow run with inputs",
382+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
383+
PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
384+
w.WriteHeader(http.StatusNoContent)
385+
}),
386+
}),
387+
requestArgs: map[string]any{
388+
"method": "run_workflow",
389+
"owner": "owner",
390+
"repo": "repo",
391+
"workflow_id": "12345",
392+
"ref": "main",
393+
"inputs": map[string]any{"FIELD1": "value1", "FIELD2": "value2"},
394+
},
395+
expectError: false,
396+
},
397+
{
398+
name: "invalid inputs type returns error",
399+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}),
400+
requestArgs: map[string]any{
401+
"method": "run_workflow",
402+
"owner": "owner",
403+
"repo": "repo",
404+
"workflow_id": "12345",
405+
"ref": "main",
406+
"inputs": "not a map",
407+
},
408+
expectError: true,
409+
expectedErrMsg: "parameter inputs is not of type map[string]interface {}, is string",
410+
},
380411
}
381412

382413
for _, tc := range tests {

pkg/github/context_tools.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
ghErrors "github.com/github/github-mcp-server/pkg/errors"
9+
"github.com/github/github-mcp-server/pkg/ifc"
910
"github.com/github/github-mcp-server/pkg/inventory"
1011
"github.com/github/github-mcp-server/pkg/scopes"
1112
"github.com/github/github-mcp-server/pkg/translations"
@@ -103,7 +104,14 @@ func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool {
103104
},
104105
}
105106

106-
return MarshalledTextResult(minimalUser), nil, nil
107+
result := MarshalledTextResult(minimalUser)
108+
if deps.GetFlags(ctx).InsidersMode {
109+
if result.Meta == nil {
110+
result.Meta = mcp.Meta{}
111+
}
112+
result.Meta["ifc"] = ifc.LabelGetMe()
113+
}
114+
return result, nil, nil
107115
},
108116
)
109117
}

pkg/github/context_tools_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,66 @@ func Test_GetMe(t *testing.T) {
139139
}
140140
}
141141

142+
func Test_GetMe_IFC_InsidersMode(t *testing.T) {
143+
t.Parallel()
144+
145+
serverTool := GetMe(translations.NullTranslationHelper)
146+
147+
mockUser := &github.User{
148+
Login: github.Ptr("testuser"),
149+
HTMLURL: github.Ptr("https://github.com/testuser"),
150+
CreatedAt: &github.Timestamp{Time: time.Now()},
151+
}
152+
mockedHTTPClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
153+
GetUser: mockResponse(t, http.StatusOK, mockUser),
154+
})
155+
156+
t.Run("insiders mode disabled omits ifc label from result meta", func(t *testing.T) {
157+
deps := BaseDeps{
158+
Client: github.NewClient(mockedHTTPClient),
159+
Flags: FeatureFlags{InsidersMode: false},
160+
}
161+
handler := serverTool.Handler(deps)
162+
163+
request := createMCPRequest(map[string]any{})
164+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
165+
require.NoError(t, err)
166+
require.False(t, result.IsError)
167+
168+
assert.Nil(t, result.Meta, "result meta should be nil when insiders mode is disabled")
169+
})
170+
171+
t.Run("insiders mode enabled includes ifc label in result meta", func(t *testing.T) {
172+
deps := BaseDeps{
173+
Client: github.NewClient(mockedHTTPClient),
174+
Flags: FeatureFlags{InsidersMode: true},
175+
}
176+
handler := serverTool.Handler(deps)
177+
178+
request := createMCPRequest(map[string]any{})
179+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
180+
require.NoError(t, err)
181+
require.False(t, result.IsError)
182+
183+
require.NotNil(t, result.Meta, "result meta should be set when insiders mode is enabled")
184+
ifcLabel, ok := result.Meta["ifc"]
185+
require.True(t, ok, "result meta should contain ifc key")
186+
187+
ifcJSON, err := json.Marshal(ifcLabel)
188+
require.NoError(t, err)
189+
190+
var ifcMap map[string]any
191+
err = json.Unmarshal(ifcJSON, &ifcMap)
192+
require.NoError(t, err)
193+
194+
assert.Equal(t, "trusted", ifcMap["integrity"])
195+
confList, ok := ifcMap["confidentiality"].([]any)
196+
require.True(t, ok, "confidentiality should be a list")
197+
require.Len(t, confList, 1)
198+
assert.Equal(t, "public", confList[0])
199+
})
200+
}
201+
142202
func Test_GetTeams(t *testing.T) {
143203
t.Parallel()
144204

pkg/github/dependabot.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTo
6969
alert, resp, err := client.Dependabot.GetRepoAlert(ctx, owner, repo, alertNumber)
7070
if err != nil {
7171
return ghErrors.NewGitHubAPIErrorResponse(ctx,
72-
fmt.Sprintf("failed to get alert with number '%d'", alertNumber),
72+
dependabotErrMsg(fmt.Sprintf("failed to get alert with number '%d'", alertNumber), owner, repo, resp),
7373
resp,
7474
err,
7575
), nil, nil
@@ -160,7 +160,7 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server
160160
})
161161
if err != nil {
162162
return ghErrors.NewGitHubAPIErrorResponse(ctx,
163-
fmt.Sprintf("failed to list alerts for repository '%s/%s'", owner, repo),
163+
dependabotErrMsg(fmt.Sprintf("failed to list alerts for repository '%s/%s'", owner, repo), owner, repo, resp),
164164
resp,
165165
err,
166166
), nil, nil
@@ -184,3 +184,16 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server
184184
},
185185
)
186186
}
187+
188+
// dependabotErrMsg enhances error messages for dependabot API failures by
189+
// appending a hint about token permissions when the response indicates
190+
// the token may lack access to the repository (403 or 404).
191+
func dependabotErrMsg(base, owner, repo string, resp *github.Response) string {
192+
if resp != nil && (resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusNotFound) {
193+
return fmt.Sprintf("%s. Your token may not have access to Dependabot alerts on %s/%s. "+
194+
"To access Dependabot alerts, the token needs the 'security_events' scope or, for fine-grained tokens, "+
195+
"Dependabot alerts read permission for this specific repository.",
196+
base, owner, repo)
197+
}
198+
return base
199+
}

pkg/github/dependabot_test.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,23 @@ func Test_GetDependabotAlert(t *testing.T) {
6666
"alertNumber": float64(9999),
6767
},
6868
expectError: true,
69-
expectedErrMsg: "failed to get alert",
69+
expectedErrMsg: "Your token may not have access to Dependabot alerts on owner/repo",
70+
},
71+
{
72+
name: "alert fetch forbidden",
73+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
74+
GetReposDependabotAlertsByOwnerByRepoByAlertNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
75+
w.WriteHeader(http.StatusForbidden)
76+
_, _ = w.Write([]byte(`{"message": "Resource not accessible by integration"}`))
77+
}),
78+
}),
79+
requestArgs: map[string]any{
80+
"owner": "owner",
81+
"repo": "repo",
82+
"alertNumber": float64(42),
83+
},
84+
expectError: true,
85+
expectedErrMsg: "Your token may not have access to Dependabot alerts on owner/repo",
7086
},
7187
}
7288

@@ -208,6 +224,21 @@ func Test_ListDependabotAlerts(t *testing.T) {
208224
expectError: true,
209225
expectedErrMsg: "failed to list alerts",
210226
},
227+
{
228+
name: "alerts listing forbidden includes token hint",
229+
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
230+
GetReposDependabotAlertsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
231+
w.WriteHeader(http.StatusForbidden)
232+
_, _ = w.Write([]byte(`{"message": "Resource not accessible by integration"}`))
233+
}),
234+
}),
235+
requestArgs: map[string]any{
236+
"owner": "owner",
237+
"repo": "repo",
238+
},
239+
expectError: true,
240+
expectedErrMsg: "Your token may not have access to Dependabot alerts on owner/repo",
241+
},
211242
}
212243

213244
for _, tc := range tests {

0 commit comments

Comments
 (0)