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
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ reference GitHub Actions by providing:
- Ready-to-use SHA-pinned references
- **Workflow analysis** with update level detection (major/minor/patch)
- **Safe update suggestions** that avoid breaking changes
- **Documentation retrieval** for actions at specific versions
- **Version comparison** to identify changes and breaking updates between
releases

## Why Use This?

Expand Down Expand Up @@ -108,6 +111,8 @@ Once configured, ask Claude to look up GitHub Actions:
- "Analyze my workflow file for outdated actions"
- "Suggest safe updates for my CI workflow"
- "What's the latest v4.x version of actions/checkout?"
- "Show me the documentation for actions/checkout@v4"
- "Compare changes between actions/setup-node@v4.0.0 and v6.0.0"

## Tool: `lookup_action`

Expand Down Expand Up @@ -248,6 +253,72 @@ Recommended Usage (SHA-pinned):
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
```

## Tool: `get_action_documentation`

Get README documentation for a GitHub Action at a specific version. Useful for
understanding how to use an action at a particular release.

### Parameters

| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------------------------------------------------------------------------- |
| `action` | string | Yes | Action reference (e.g., `actions/checkout` or `actions/checkout@v4`) |
| `ref` | string | No | Optional ref override (tag/branch/commit). Defaults to version or main branch |

### Example Output

```
# actions/checkout Documentation
Ref: v4.2.0

---

[Full README markdown content for the action at the specified version]
```

## Tool: `compare_action_versions`

Compare changes between two versions of a GitHub Action. Shows release notes and
identifies version update levels to help with upgrade decisions.

### Parameters

| Parameter | Type | Required | Description |
| ---------------- | ------ | -------- | ------------------------------------------------------------- |
| `action` | string | Yes | Action with current version (e.g., `actions/checkout@v4.0.2`) |
| `target_version` | string | No | Target version (defaults to latest) |

### Example Output

```
# Version Comparison: actions/checkout

From: v4.0.0
To: v4.2.0

## Summary
- Total releases: 3
- Major updates: 0
- Minor updates: 2
- Patch updates: 1

## Release History (chronological)

### v4.1.0 (2025-02-15) - Minor Update
Added support for sparse checkouts and improved performance.

### v4.1.1 (2025-02-20) - Patch Update
Fixed bug with submodule handling on Windows.

### v4.2.0 (2025-03-01) - Minor Update
Added new input parameter for custom checkout paths.

---

Note: Major version updates (marked with ⚠️) may contain breaking changes.
Review the release notes above to understand the impact of each update.
```

## Authentication

The service supports multiple authentication methods, checked in the following
Expand Down
100 changes: 100 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ import {
getLatestInMajorVersion,
suggestUpdates,
} from "./src/tools/suggest-updates.ts";
import {
formatDocumentationResultAsText,
getActionDocumentation,
} from "./src/tools/get-action-documentation.ts";
import {
compareActionVersions,
formatCompareResultAsText,
} from "./src/tools/compare-action-versions.ts";

// Create the MCP server
const server = new McpServer({
Expand Down Expand Up @@ -213,6 +221,98 @@ server.tool(
},
);

// Register the get_action_documentation tool
server.tool(
"get_action_documentation",
"Get README documentation for a GitHub Action at a specific version. " +
"Useful for understanding how to use an action at a particular release.",
{
action: z
.string()
.describe(
"Action reference (e.g., 'actions/checkout' or 'actions/checkout@v4')",
),
ref: z
.string()
.optional()
.describe("Optional ref override (tag/branch/commit)"),
},
async ({ action, ref }) => {
try {
const result = await getActionDocumentation({ action, ref });
const text = formatDocumentationResultAsText(result);

return {
content: [
{
type: "text" as const,
text,
},
],
};
} catch (error) {
const message = error instanceof Error
? error.message
: "Unknown error occurred";
return {
content: [
{
type: "text" as const,
text: `Error: ${message}`,
},
],
isError: true,
};
}
},
);

// Register the compare_action_versions tool
server.tool(
"compare_action_versions",
"Compare changes between two versions of a GitHub Action. " +
"Shows release notes and identifies version update levels to help with upgrade decisions.",
{
action: z
.string()
.describe(
"Action with current version (e.g., 'actions/checkout@v4.0.2')",
),
target_version: z
.string()
.optional()
.describe("Target version (defaults to latest)"),
},
async ({ action, target_version }) => {
try {
const result = await compareActionVersions({ action, target_version });
const text = formatCompareResultAsText(result);

return {
content: [
{
type: "text" as const,
text,
},
],
};
} catch (error) {
const message = error instanceof Error
? error.message
: "Unknown error occurred";
return {
content: [
{
type: "text" as const,
text: `Error: ${message}`,
},
],
isError: true,
};
}
},
);

// Start the server with stdio transport
async function main() {
const transport = new StdioServerTransport();
Expand Down
69 changes: 69 additions & 0 deletions src/github/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
GitHubError,
GitHubRef,
GitHubRelease,
GitHubRepository,
GitHubTag,
RateLimitInfo,
} from "./types.ts";
Expand Down Expand Up @@ -323,4 +324,72 @@ export class GitHubClient {

return matchingReleases[0] || null;
}

/**
* Get repository information including default branch
*/
async getRepository(owner: string, repo: string): Promise<GitHubRepository> {
const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}`;
return await this.fetch<GitHubRepository>(url);
}

/**
* Get repository default branch name
*/
async getDefaultBranch(owner: string, repo: string): Promise<string> {
const repository = await this.getRepository(owner, repo);
return repository.default_branch;
}

/**
* Get file content from repository at specific ref
* @param owner - Repository owner
* @param repo - Repository name
* @param path - File path (e.g., "README.md")
* @param ref - Branch, tag, or commit SHA (optional, defaults to repo default branch)
*/
async getFileContent(
owner: string,
repo: string,
path: string,
ref?: string,
): Promise<string> {
// Ensure token is resolved before making request
await this.ensureToken();

// Build URL with optional ref parameter
let url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/contents/${path}`;
if (ref) {
url += `?ref=${encodeURIComponent(ref)}`;
}

// Use Accept header to get raw content instead of JSON
const response = await fetch(url, {
headers: {
...this.getHeaders(),
Accept: "application/vnd.github.raw",
},
});

this.updateRateLimitInfo(response);

if (!response.ok) {
const error: GitHubError = await response.json();
if (response.status === 404) {
throw new Error(
`File not found: ${path}${ref ? ` at ref ${ref}` : ""}`,
);
}
if (response.status === 403 && this.rateLimitInfo?.remaining === 0) {
const resetDate = new Date(this.rateLimitInfo.reset * 1000);
throw new Error(
`Rate limit exceeded. Resets at ${resetDate.toISOString()}. ` +
`Consider setting GITHUB_TOKEN for higher limits.`,
);
}
throw new Error(`GitHub API error: ${error.message}`);
}

return response.text();
}
}
17 changes: 17 additions & 0 deletions src/github/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface GitHubRelease {
created_at: string;
published_at: string | null;
html_url: string;
/** Release notes in markdown format */
body?: string | null;
/** Whether or not the release is immutable (protected from modification) */
immutable?: boolean;
assets: GitHubAsset[];
Expand Down Expand Up @@ -64,3 +66,18 @@ export interface RateLimitInfo {
reset: number;
used: number;
}

export interface GitHubContent {
name: string;
path: string;
sha: string;
size: number;
type: "file" | "dir";
content?: string; // base64 encoded
encoding?: string; // "base64"
download_url: string | null;
}

export interface GitHubRepository {
default_branch: string;
}
Loading