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
7 changes: 6 additions & 1 deletion actions/setup/js/parse_pi_log.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,12 @@ describe("parse_pi_log.cjs", () => {

it("detects the v3 schema and rejects the legacy schema", () => {
expect(isPiV3Schema(v3Lines)).toBe(true);
expect(isPiV3Schema([{ type: "init", model: "pi-3" }, { type: "assistant", content: "hi" }])).toBe(false);
expect(
isPiV3Schema([
{ type: "init", model: "pi-3" },
{ type: "assistant", content: "hi" },
])
).toBe(false);
});

it("renders assistant text, tool calls, and tool results from a v3 stream", () => {
Expand Down
10 changes: 5 additions & 5 deletions eslint-factory/src/rules/require-return-after-core-setfailed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,21 +353,21 @@ export const requireReturnAfterCoreSetFailedRule = createRule({
const ancestors = sourceCode.getAncestors(stmt);
const continuation = findContinuationOutsideBlock(stmt, ancestors);
if (continuation !== null && !isControlTransfer(continuation)) {
reportNested(stmt);
reportNested(stmt);
}
}

function checkDirectControlBody(stmt: TSESTree.Statement | null): void {
if (stmt !== null && stmt.type !== AST_NODE_TYPES.BlockStatement && isCoreSetFailedStatement(stmt)) {
checkNestedContinuation(stmt);
checkNestedContinuation(stmt);
}
}

return {
IfStatement(node: TSESTree.IfStatement) {
for (const branch of [node.consequent, node.alternate]) {
checkDirectControlBody(branch);
}
for (const branch of [node.consequent, node.alternate]) {
checkDirectControlBody(branch);
}
},
// Check statement blocks: if body, else body, while body, function body, etc.
BlockStatement(node: TSESTree.BlockStatement) {
Expand Down
11 changes: 9 additions & 2 deletions pkg/cli/mcp_inspect_mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func connectStdioMCPServer(ctx context.Context, config parser.RegistryMCPServerC
}

// Create MCP client and connect
client := mcp.NewClient(&mcp.Implementation{Name: "gh-aw-inspector", Version: "1.0.0"}, &mcp.ClientOptions{
client := mcp.NewClient(mcpInspectClientImplementation(), &mcp.ClientOptions{
Logger: logger.NewSlogLoggerWithHandler(mcpInspectServerLog),
})
transport := &mcp.CommandTransport{Command: cmd}
Expand Down Expand Up @@ -235,7 +235,7 @@ func connectHTTPMCPServer(ctx context.Context, config parser.RegistryMCPServerCo
}

// Create MCP client with logger for better debugging
client := mcp.NewClient(&mcp.Implementation{Name: "gh-aw-inspector", Version: "1.0.0"}, &mcp.ClientOptions{
client := mcp.NewClient(mcpInspectClientImplementation(), &mcp.ClientOptions{
Logger: logger.NewSlogLoggerWithHandler(mcpInspectServerLog),
})

Expand Down Expand Up @@ -347,6 +347,13 @@ func extractRootsFromResources(resources []*mcp.Resource) []*mcp.Root {
return roots
}

func mcpInspectClientImplementation() *mcp.Implementation {
return &mcp.Implementation{
Name: "gh-aw-inspector",
Version: GetVersion(),
}
}

// displayServerCapabilities shows the server's tools, resources, and roots in formatted tables
func displayServerCapabilities(info *parser.MCPServerInfo, toolFilter string) {
mcpInspectServerLog.Printf("Displaying server capabilities: tools=%d, resources=%d, toolFilter=%q", len(info.Tools), len(info.Resources), toolFilter)
Expand Down
31 changes: 31 additions & 0 deletions pkg/cli/mcp_inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package cli

import (
"encoding/base64"
"strings"
"testing"

Expand All @@ -27,6 +28,36 @@ func TestMCPInspectSubcommand_NoArgBehaviorDocumented(t *testing.T) {
}
}

func TestMCPInspectClientImplementation_UsesCLIGetVersion(t *testing.T) {
implementation := mcpInspectClientImplementation()

if implementation.Name != "gh-aw-inspector" {
t.Fatalf("expected inspector client name %q, got %q", "gh-aw-inspector", implementation.Name)
}
if implementation.Version != GetVersion() {
t.Fatalf("expected inspector client version %q, got %q", GetVersion(), implementation.Version)
}
}

func TestMCPEmojiIconSource_ReturnsDataURI(t *testing.T) {
source := mcpEmojiIconSource("📊")
if !strings.HasPrefix(source, "data:image/svg+xml;base64,") {
t.Fatalf("expected base64 data URI source, got %q", source)
}

decoded, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(source, "data:image/svg+xml;base64,"))
if err != nil {
t.Fatalf("failed to base64-decode icon source: %v", err)
}

if !strings.Contains(string(decoded), "<svg") {
t.Fatalf("expected SVG payload, got %q", decoded)
}
if !strings.Contains(string(decoded), "📊") {
t.Fatalf("expected original emoji in SVG payload, got %q", decoded)
}
}

func TestValidateServerSecrets(t *testing.T) {
tests := []struct {
name string
Expand Down
22 changes: 11 additions & 11 deletions pkg/cli/mcp_server_tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,16 +260,16 @@ func TestMCPServer_ToolIcons(t *testing.T) {

// Expected icons for each tool
expectedIcons := map[string]string{
"status": "📊",
"compile": "📋",
"logs": "📝",
"audit": "🔍",
"audit-diff": "🔎",
"checks": "✅",
"mcp-inspect": "🔬",
"add": "➕",
"update": "🔄",
"fix": "🔧",
"status": mcpEmojiIconSource("📊"),
"compile": mcpEmojiIconSource("📋"),
"logs": mcpEmojiIconSource("📝"),
"audit": mcpEmojiIconSource("🔍"),
"audit-diff": mcpEmojiIconSource("🔎"),
"checks": mcpEmojiIconSource("✅"),
"mcp-inspect": mcpEmojiIconSource("🔬"),
"add": mcpEmojiIconSource("➕"),
"update": mcpEmojiIconSource("🔄"),
"fix": mcpEmojiIconSource("🔧"),
}

// Verify each tool has an icon
Expand All @@ -279,7 +279,7 @@ func TestMCPServer_ToolIcons(t *testing.T) {
continue
}

// Check that the icon source matches expected emoji
// Check that the icon source matches the expected spec-compliant URI
if expectedIcon, ok := expectedIcons[tool.Name]; ok {
if tool.Icons[0].Source != expectedIcon {
t.Errorf("Tool '%s' has unexpected icon. Expected: %s, Got: %s",
Expand Down
17 changes: 17 additions & 0 deletions pkg/cli/mcp_tool_icons.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cli

import (
"encoding/base64"
"fmt"

"github.com/modelcontextprotocol/go-sdk/mcp"
)

func mcpToolIcons(emoji string) []mcp.Icon {
return []mcp.Icon{{Source: mcpEmojiIconSource(emoji)}}
}

func mcpEmojiIconSource(emoji string) string {
svg := fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><text x="16" y="24" font-size="24" text-anchor="middle">%s</text></svg>`, emoji)
return "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg))
}
12 changes: 3 additions & 9 deletions pkg/cli/mcp_tools_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ func registerAddTool(server *mcp.Server, execCmd execCmdFunc) {
OpenWorldHint: new(true),
},
Description: "Add workflows from remote repositories to .github/workflows",
Icons: []mcp.Icon{
{Source: "➕"},
},
Icons: mcpToolIcons("➕"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args addArgs) (*mcp.CallToolResult, any, error) {
// Check for cancellation before starting
select {
Expand Down Expand Up @@ -123,9 +121,7 @@ Returns formatted text output showing:
- Extension update status
- Updated workflows with their new versions
- Compilation status for each updated workflow`,
Icons: []mcp.Icon{
{Source: "🔄"},
},
Icons: mcpToolIcons("🔄"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args updateArgs) (*mcp.CallToolResult, any, error) {
// Check for cancellation before starting
select {
Expand Down Expand Up @@ -204,9 +200,7 @@ Returns formatted text output showing:
- List of workflow files processed
- Which codemods were applied to each file
- Summary of fixes applied`,
Icons: []mcp.Icon{
{Source: "🔧"},
},
Icons: mcpToolIcons("🔧"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args fixArgs) (*mcp.CallToolResult, any, error) {
// Check for cancellation before starting
select {
Expand Down
12 changes: 3 additions & 9 deletions pkg/cli/mcp_tools_privileged.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,7 @@ Check for the presence of the continuation field to determine if there are more
The continuation field includes all necessary parameters (before_run_id, etc.) to resume fetching
from where the previous request stopped due to timeout.`,
InputSchema: logsSchema,
Icons: []mcp.Icon{
{Source: "📝"},
},
Icons: mcpToolIcons("📝"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args logsArgs) (*mcp.CallToolResult, any, error) {
// Check actor permissions first
if err := checkActorPermission(ctx, actor, validateActor, "logs"); err != nil {
Expand Down Expand Up @@ -373,9 +371,7 @@ Single-run returns JSON with:

Multi-run diff returns JSON describing changes between the base and each comparison run.`,
InputSchema: auditSchema,
Icons: []mcp.Icon{
{Source: "🔍"},
},
Icons: mcpToolIcons("🔍"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args auditArgs) (*mcp.CallToolResult, any, error) {
// Check actor permissions first
if err := checkActorPermission(ctx, actor, validateActor, "audit"); err != nil {
Expand Down Expand Up @@ -537,9 +533,7 @@ then produces a diff showing:

Returns JSON describing the differences between the base run and each comparison run.`,
InputSchema: schema,
Icons: []mcp.Icon{
{Source: "🔎"},
},
Icons: mcpToolIcons("🔎"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args auditDiffArgs) (*mcp.CallToolResult, any, error) {
if err := checkActorPermission(ctx, actor, validateActor, "audit-diff"); err != nil {
return nil, nil, err
Expand Down
16 changes: 4 additions & 12 deletions pkg/cli/mcp_tools_readonly.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ Returns a JSON array where each element has the following structure:
- compiled: Whether the workflow is compiled ("Yes", "No", or "N/A")
- status: GitHub workflow status ("active", "disabled", "Unknown")
- time_remaining: Time remaining until workflow deadline (if applicable)`,
Icons: []mcp.Icon{
{Source: "📊"},
},
Icons: mcpToolIcons("📊"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args statusArgs) (*mcp.CallToolResult, any, error) {
// Check for cancellation before starting
select {
Expand Down Expand Up @@ -123,9 +121,7 @@ Returns JSON array with validation results for each workflow:
- warnings: Array of warning objects
- compiled_file: Path to the generated .lock.yml file`,
InputSchema: compileSchema,
Icons: []mcp.Icon{
{Source: "📋"},
},
Icons: mcpToolIcons("📋"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args compileArgs) (*mcp.CallToolResult, any, error) {
// Check for cancellation before starting
select {
Expand Down Expand Up @@ -298,9 +294,7 @@ Returns formatted text output showing:
- Tools, resources, and roots exposed by each server
- Secret availability status (if GitHub token is available)
- Detailed tool information when tool parameter is specified`,
Icons: []mcp.Icon{
{Source: "🔬"},
},
Icons: mcpToolIcons("🔬"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args mcpInspectArgs) (*mcp.CallToolResult, any, error) {
// Check for cancellation before starting
select {
Expand Down Expand Up @@ -378,9 +372,7 @@ Use required_state as the authoritative CI verdict in repos that have optional
deployment integrations posting commit statuses alongside required CI checks.

Also returns pr_number, head_sha, check_runs, statuses, and total_count.`,
Icons: []mcp.Icon{
{Source: "✅"},
},
Icons: mcpToolIcons("✅"),
}, func(ctx context.Context, req *mcp.CallToolRequest, args checksArgs) (*mcp.CallToolResult, any, error) {
// Check for cancellation before starting
select {
Expand Down
Loading