Skip to content

Commit d331b8d

Browse files
author
root
committed
Add repository focus mode for project-scoped MCP workflows
Bind a stdio server instance to a default owner/repo so agents can call get_repository_context instead of searching repositories first, with optional discovery-tool filtering and fine-grained PAT access hints. Closes #1683
1 parent 457f599 commit d331b8d

18 files changed

Lines changed: 670 additions & 2 deletions

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,9 @@ The following sets of tools are available:
670670
- **get_me** - Get my user profile
671671
- No parameters required
672672

673+
- **get_repository_context** - Get repository context
674+
- No parameters required
675+
673676
- **get_team_members** - Get team members
674677
- **Required OAuth Scopes**: `read:org`
675678
- **Accepted OAuth Scopes**: `admin:org`, `read:org`, `write:org`

cmd/github-mcp-server/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ var (
9494
InsidersMode: viper.GetBool("insiders"),
9595
ExcludeTools: excludeTools,
9696
RepoAccessCacheTTL: &ttl,
97+
DefaultRepository: viper.GetString("repository"),
98+
AllowDiscoveryTools: viper.GetBool("allow-discovery-tools"),
9799
}
98100
return ghmcp.RunStdioServer(stdioServerConfig)
99101
},
@@ -181,6 +183,8 @@ func init() {
181183
rootCmd.PersistentFlags().Bool("lockdown-mode", false, "Enable lockdown mode")
182184
rootCmd.PersistentFlags().Bool("insiders", false, "Enable insiders features")
183185
rootCmd.PersistentFlags().Duration("repo-access-cache-ttl", 5*time.Minute, "Override the repo access cache TTL (e.g. 1m, 0s to disable)")
186+
rootCmd.PersistentFlags().String("repository", "", "Default owner/repo for project-focused mode (also GITHUB_REPOSITORY env var)")
187+
rootCmd.PersistentFlags().Bool("allow-discovery-tools", false, "Keep open-world discovery tools when --repository is set")
184188

185189
// HTTP-specific flags
186190
httpCmd.Flags().Int("port", 8082, "HTTP server port")
@@ -203,6 +207,8 @@ func init() {
203207
_ = viper.BindPFlag("lockdown-mode", rootCmd.PersistentFlags().Lookup("lockdown-mode"))
204208
_ = viper.BindPFlag("insiders", rootCmd.PersistentFlags().Lookup("insiders"))
205209
_ = viper.BindPFlag("repo-access-cache-ttl", rootCmd.PersistentFlags().Lookup("repo-access-cache-ttl"))
210+
_ = viper.BindPFlag("repository", rootCmd.PersistentFlags().Lookup("repository"))
211+
_ = viper.BindPFlag("allow-discovery-tools", rootCmd.PersistentFlags().Lookup("allow-discovery-tools"))
206212
_ = viper.BindPFlag("port", httpCmd.Flags().Lookup("port"))
207213
_ = viper.BindPFlag("base-url", httpCmd.Flags().Lookup("base-url"))
208214
_ = viper.BindPFlag("base-path", httpCmd.Flags().Lookup("base-path"))

docs/server-configuration.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ We currently support the following ways in which the GitHub MCP Server can be co
1515
| Insiders Mode | `X-MCP-Insiders` header or `/insiders` URL | `--insiders` flag or `GITHUB_INSIDERS` env var |
1616
| Feature Flags | `X-MCP-Features` header | `--features` flag |
1717
| Scope Filtering | Always enabled | Always enabled |
18+
| Default Repository | Not available | `--repository` flag or `GITHUB_REPOSITORY` env var |
1819
| Server Name/Title | Not available | `GITHUB_MCP_SERVER_NAME` / `GITHUB_MCP_SERVER_TITLE` env vars or `github-mcp-server-config.json` |
1920

2021
> **Default behavior:** If you don't specify any configuration, the server uses the **default toolsets**: `context`, `issues`, `pull_requests`, `repos`, `users`.
@@ -446,6 +447,49 @@ MCP Apps is enabled by [Insiders Mode](#insiders-mode), or independently via the
446447

447448
---
448449

450+
### Repository Focus Mode
451+
452+
**Best for:** single-repository development workflows where agents should behave like `gh` inside a git checkout—working directly on one project instead of searching across your account first.
453+
454+
Set a default repository with `--repository owner/repo` or the `GITHUB_REPOSITORY` environment variable. The value accepts common formats such as `owner/repo`, `https://github.com/owner/repo`, or `git@github.com:owner/repo.git`.
455+
456+
When a default repository is configured:
457+
458+
- The `get_repository_context` tool returns the configured owner/repo and verifies token access (including fine-grained PAT permission hints when access fails).
459+
- Server instructions tell agents to call `get_repository_context` first and use the configured owner/repo with repo-scoped tools like `list_issues`.
460+
- Open-world discovery tools (`search_repositories`, `search_users`, `search_orgs`, `list_starred_repositories`, `create_repository`, `fork_repository`) are hidden unless you pass `--allow-discovery-tools`.
461+
462+
**Example (local server):**
463+
464+
```json
465+
{
466+
"type": "stdio",
467+
"command": "docker",
468+
"args": [
469+
"run",
470+
"-i",
471+
"--rm",
472+
"-e",
473+
"GITHUB_PERSONAL_ACCESS_TOKEN",
474+
"-e",
475+
"GITHUB_REPOSITORY",
476+
"ghcr.io/github/github-mcp-server",
477+
"stdio",
478+
"--read-only",
479+
"--toolsets",
480+
"context,issues,pull_requests"
481+
],
482+
"env": {
483+
"GITHUB_PERSONAL_ACCESS_TOKEN": "${input:github_token}",
484+
"GITHUB_REPOSITORY": "owner/repo"
485+
}
486+
}
487+
```
488+
489+
> **Note:** The MCP server cannot read your local `.git` directory directly. Configure `GITHUB_REPOSITORY` in your MCP host settings (or pass `--repository`) to bind an instance to the project you are working on.
490+
491+
---
492+
449493
### Scope Filtering
450494

451495
**Automatic feature:** The server handles OAuth scopes differently depending on authentication type:
@@ -467,6 +511,8 @@ See [Scope Filtering](./scope-filtering.md) for details on how filtering works w
467511
| Server fails to start | Invalid tool name in `--tools` or `X-MCP-Tools` | Check tool name spelling; use exact names from [Tools list](../README.md#tools) |
468512
| Write tools not working | Read-only mode enabled | Remove `--read-only` flag or `X-MCP-Readonly` header |
469513
| Tools missing | Toolset not enabled | Add the required toolset or specific tool |
514+
| Agent searches all repos first | No default repository configured | Set `GITHUB_REPOSITORY` or `--repository owner/repo` and call `get_repository_context` |
515+
| Fine-grained PAT cannot access collaborator repo | Token not authorized for that repository | Grant repository access and required permissions on the fine-grained PAT; check `get_repository_context` hint |
470516

471517
---
472518

internal/ghmcp/server.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se
135135
if err != nil {
136136
return nil, fmt.Errorf("failed to create observability exporters: %w", err)
137137
}
138+
repositoryContext, err := github.BuildRepositoryContextConfig(cfg.DefaultRepository, cfg.Token, cfg.AllowDiscoveryTools)
139+
if err != nil {
140+
return nil, fmt.Errorf("failed to parse default repository: %w", err)
141+
}
138142
deps := github.NewBaseDeps(
139143
clients.rest,
140144
clients.gql,
@@ -146,8 +150,36 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se
146150
},
147151
cfg.ContentWindowSize,
148152
featureChecker,
153+
repositoryContext,
149154
obs,
150155
)
156+
if repositoryContext.DefaultRepository != nil {
157+
access := github.VerifyRepositoryAccess(
158+
ctx,
159+
clients.rest,
160+
*repositoryContext.DefaultRepository,
161+
github.DetectTokenType(cfg.Token),
162+
)
163+
if access.Accessible {
164+
cfg.Logger.Info(
165+
"default repository access verified",
166+
"repository",
167+
repositoryContext.DefaultRepository.FullName,
168+
"private",
169+
access.Private,
170+
)
171+
} else {
172+
cfg.Logger.Warn(
173+
"default repository is not accessible with the current token",
174+
"repository",
175+
repositoryContext.DefaultRepository.FullName,
176+
"error",
177+
access.Error,
178+
"hint",
179+
access.Hint,
180+
)
181+
}
182+
}
151183
// Build and register the tool/resource/prompt inventory
152184
inventoryBuilder := github.NewInventory(cfg.Translator).
153185
WithDeprecatedAliases(github.DeprecatedToolAliases).
@@ -156,8 +188,14 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se
156188
WithTools(github.CleanTools(cfg.EnabledTools)).
157189
WithExcludeTools(cfg.ExcludeTools).
158190
WithServerInstructions().
191+
WithDefaultRepository(cfg.DefaultRepository).
192+
WithFocusRepository(repositoryContext.FocusMode).
159193
WithFeatureChecker(featureChecker)
160194

195+
if repositoryContext.FocusMode {
196+
inventoryBuilder = inventoryBuilder.WithFilter(github.CreateRepositoryFocusFilter(true))
197+
}
198+
161199
// Apply token scope filtering if scopes are known (for PAT filtering)
162200
if cfg.TokenScopes != nil {
163201
inventoryBuilder = inventoryBuilder.WithFilter(github.CreateToolScopeFilter(cfg.TokenScopes))
@@ -229,6 +267,12 @@ type StdioServerConfig struct {
229267

230268
// RepoAccessCacheTTL overrides the default TTL for repository access cache entries.
231269
RepoAccessCacheTTL *time.Duration
270+
271+
// DefaultRepository scopes the server to a single owner/repo (owner/repo format).
272+
DefaultRepository string
273+
274+
// AllowDiscoveryTools keeps open-world discovery tools when DefaultRepository is set.
275+
AllowDiscoveryTools bool
232276
}
233277

234278
// RunStdioServer is not concurrent safe.
@@ -287,6 +331,8 @@ func RunStdioServer(cfg StdioServerConfig) error {
287331
Logger: logger,
288332
RepoAccessTTL: cfg.RepoAccessCacheTTL,
289333
TokenScopes: tokenScopes,
334+
DefaultRepository: cfg.DefaultRepository,
335+
AllowDiscoveryTools: cfg.AllowDiscoveryTools,
290336
})
291337
if err != nil {
292338
return fmt.Errorf("failed to create MCP server: %w", err)

pkg/github/context_tools_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ func Test_GetMe_IFC_FeatureFlag(t *testing.T) {
162162
func(_ context.Context, flagName string) (bool, error) {
163163
return flagName == FeatureFlagIFCLabels && enabled, nil
164164
},
165+
RepositoryContextConfig{},
165166
stubExporters(),
166167
)
167168
}

pkg/github/dependencies.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ type ToolDependencies interface {
105105

106106
// Metrics returns the metrics client
107107
Metrics(ctx context.Context) metrics.Metrics
108+
109+
// GetRepositoryContext returns configured default repository context for project-focused mode.
110+
GetRepositoryContext() RepositoryContextConfig
111+
}
112+
113+
// RepositoryContextConfig holds server-level repository scoping configuration.
114+
type RepositoryContextConfig struct {
115+
DefaultRepository *RepositoryRef
116+
FocusMode bool
117+
Token string
108118
}
109119

110120
// BaseDeps is the standard implementation of ToolDependencies for the local server.
@@ -125,6 +135,9 @@ type BaseDeps struct {
125135
// Feature flag checker for runtime checks
126136
featureChecker inventory.FeatureFlagChecker
127137

138+
// Repository context for project-focused mode
139+
RepositoryContext RepositoryContextConfig
140+
128141
// Observability exporters (includes logger)
129142
Obsv observability.Exporters
130143
}
@@ -142,6 +155,7 @@ func NewBaseDeps(
142155
flags FeatureFlags,
143156
contentWindowSize int,
144157
featureChecker inventory.FeatureFlagChecker,
158+
repositoryContext RepositoryContextConfig,
145159
obsv observability.Exporters,
146160
) *BaseDeps {
147161
return &BaseDeps{
@@ -153,6 +167,7 @@ func NewBaseDeps(
153167
Flags: flags,
154168
ContentWindowSize: contentWindowSize,
155169
featureChecker: featureChecker,
170+
RepositoryContext: repositoryContext,
156171
Obsv: obsv,
157172
}
158173
}
@@ -196,6 +211,11 @@ func (d BaseDeps) Metrics(ctx context.Context) metrics.Metrics {
196211
return d.Obsv.Metrics(ctx)
197212
}
198213

214+
// GetRepositoryContext implements ToolDependencies.
215+
func (d BaseDeps) GetRepositoryContext() RepositoryContextConfig {
216+
return d.RepositoryContext
217+
}
218+
199219
// IsFeatureEnabled checks if a feature flag is enabled.
200220
// Returns false if the feature checker is nil, flag name is empty, or an error occurs.
201221
// This allows tools to conditionally change behavior based on feature flags.
@@ -441,3 +461,8 @@ func (d *RequestDeps) IsFeatureEnabled(ctx context.Context, flagName string) boo
441461

442462
return enabled
443463
}
464+
465+
// GetRepositoryContext implements ToolDependencies.
466+
func (d *RequestDeps) GetRepositoryContext() RepositoryContextConfig {
467+
return RepositoryContextConfig{}
468+
}

pkg/github/dependencies_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func TestIsFeatureEnabled_WithEnabledFlag(t *testing.T) {
3636
github.FeatureFlags{},
3737
0, // contentWindowSize
3838
checker, // featureChecker
39+
github.RepositoryContextConfig{},
3940
testExporters(),
4041
)
4142

@@ -61,6 +62,7 @@ func TestIsFeatureEnabled_WithoutChecker(t *testing.T) {
6162
github.FeatureFlags{},
6263
0, // contentWindowSize
6364
nil, // featureChecker (nil)
65+
github.RepositoryContextConfig{},
6466
testExporters(),
6567
)
6668

@@ -86,6 +88,7 @@ func TestIsFeatureEnabled_EmptyFlagName(t *testing.T) {
8688
github.FeatureFlags{},
8789
0, // contentWindowSize
8890
checker, // featureChecker
91+
github.RepositoryContextConfig{},
8992
testExporters(),
9093
)
9194

@@ -111,6 +114,7 @@ func TestIsFeatureEnabled_CheckerError(t *testing.T) {
111114
github.FeatureFlags{},
112115
0, // contentWindowSize
113116
checker, // featureChecker
117+
github.RepositoryContextConfig{},
114118
testExporters(),
115119
)
116120

pkg/github/feature_flags_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ func TestHelloWorld_ConditionalBehavior_Featureflag(t *testing.T) {
102102
FeatureFlags{},
103103
0,
104104
featureCheckerFor(enabledFlags...),
105+
RepositoryContextConfig{},
105106
stubExporters(),
106107
)
107108

0 commit comments

Comments
 (0)