Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion pkg/github/context_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/ifc"
"github.com/github/github-mcp-server/pkg/inventory"
"github.com/github/github-mcp-server/pkg/scopes"
"github.com/github/github-mcp-server/pkg/translations"
Expand Down Expand Up @@ -103,7 +104,14 @@ func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool {
},
}

return MarshalledTextResult(minimalUser), nil, nil
result := MarshalledTextResult(minimalUser)
if deps.GetFlags(ctx).InsidersMode {
if result.Meta == nil {
result.Meta = mcp.Meta{}
}
result.Meta["ifc"] = ifc.LabelGetMe()
}
return result, nil, nil
Comment on lines +107 to +114
},
)
}
Expand Down
60 changes: 60 additions & 0 deletions pkg/github/context_tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,66 @@ func Test_GetMe(t *testing.T) {
}
}

func Test_GetMe_IFC_InsidersMode(t *testing.T) {
t.Parallel()

serverTool := GetMe(translations.NullTranslationHelper)

mockUser := &github.User{
Login: github.Ptr("testuser"),
HTMLURL: github.Ptr("https://github.com/testuser"),
CreatedAt: &github.Timestamp{Time: time.Now()},
}
mockedHTTPClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
GetUser: mockResponse(t, http.StatusOK, mockUser),
})

t.Run("insiders mode disabled omits ifc label from result meta", func(t *testing.T) {
deps := BaseDeps{
Client: github.NewClient(mockedHTTPClient),
Flags: FeatureFlags{InsidersMode: false},
}
handler := serverTool.Handler(deps)

request := createMCPRequest(map[string]any{})
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
require.NoError(t, err)
require.False(t, result.IsError)

assert.Nil(t, result.Meta, "result meta should be nil when insiders mode is disabled")
})

t.Run("insiders mode enabled includes ifc label in result meta", func(t *testing.T) {
deps := BaseDeps{
Client: github.NewClient(mockedHTTPClient),
Flags: FeatureFlags{InsidersMode: true},
}
handler := serverTool.Handler(deps)

request := createMCPRequest(map[string]any{})
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
require.NoError(t, err)
require.False(t, result.IsError)

require.NotNil(t, result.Meta, "result meta should be set when insiders mode is enabled")
ifcLabel, ok := result.Meta["ifc"]
require.True(t, ok, "result meta should contain ifc key")

ifcJSON, err := json.Marshal(ifcLabel)
require.NoError(t, err)

var ifcMap map[string]any
err = json.Unmarshal(ifcJSON, &ifcMap)
require.NoError(t, err)

assert.Equal(t, "trusted", ifcMap["integrity"])
confList, ok := ifcMap["confidentiality"].([]any)
require.True(t, ok, "confidentiality should be a list")
require.Len(t, confList, 1)
assert.Equal(t, "public", confList[0])
})
}

func Test_GetTeams(t *testing.T) {
t.Parallel()

Expand Down
29 changes: 29 additions & 0 deletions pkg/ifc/ifc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Package ifc provides Information Flow Control labels for annotating MCP tool outputs.
// The actual IFC enforcement engine lives in a separate service; this package only
// defines the label schema used for annotations.
package ifc

type Integrity string

const (
IntegrityTrusted Integrity = "trusted"
IntegrityUntrusted Integrity = "untrusted"
)

type Confidentiality string

const (
ConfidentialityPublic Confidentiality = "public"
)

type SecurityLabel struct {
Integrity Integrity `json:"integrity"`
Confidentiality []Confidentiality `json:"confidentiality"`
}

func LabelGetMe() SecurityLabel {
return SecurityLabel{
Integrity: IntegrityTrusted,
Confidentiality: []Confidentiality{ConfidentialityPublic},
}
}
6 changes: 3 additions & 3 deletions script/get-me
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ output=$(
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"get-me-script","version":"1.0.0"}}}'
echo '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_me","arguments":{}}}'
sleep 1
) | go run cmd/github-mcp-server/main.go stdio 2>/dev/null | tail -1
sleep 3
) | go run cmd/github-mcp-server/main.go stdio "$@" 2>/dev/null | grep '"id":2'
)

if command -v jq &> /dev/null; then
echo "$output" | jq '.result.content[0].text | fromjson'
echo "$output" | jq '{_meta: .result._meta, content: (.result.content[0].text | fromjson)}'
else
echo "$output"
fi
Loading