Skip to content
This repository was archived by the owner on Apr 22, 2026. It is now read-only.

Commit 9a5de67

Browse files
feat: add documentation and version comparison tools (#4)
2 parents b32381d + e95abbf commit 9a5de67

6 files changed

Lines changed: 635 additions & 0 deletions

File tree

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ reference GitHub Actions by providing:
99
- Ready-to-use SHA-pinned references
1010
- **Workflow analysis** with update level detection (major/minor/patch)
1111
- **Safe update suggestions** that avoid breaking changes
12+
- **Documentation retrieval** for actions at specific versions
13+
- **Version comparison** to identify changes and breaking updates between
14+
releases
1215

1316
## Why Use This?
1417

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

112117
## Tool: `lookup_action`
113118

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

256+
## Tool: `get_action_documentation`
257+
258+
Get README documentation for a GitHub Action at a specific version. Useful for
259+
understanding how to use an action at a particular release.
260+
261+
### Parameters
262+
263+
| Parameter | Type | Required | Description |
264+
| --------- | ------ | -------- | ----------------------------------------------------------------------------- |
265+
| `action` | string | Yes | Action reference (e.g., `actions/checkout` or `actions/checkout@v4`) |
266+
| `ref` | string | No | Optional ref override (tag/branch/commit). Defaults to version or main branch |
267+
268+
### Example Output
269+
270+
```
271+
# actions/checkout Documentation
272+
Ref: v4.2.0
273+
274+
---
275+
276+
[Full README markdown content for the action at the specified version]
277+
```
278+
279+
## Tool: `compare_action_versions`
280+
281+
Compare changes between two versions of a GitHub Action. Shows release notes and
282+
identifies version update levels to help with upgrade decisions.
283+
284+
### Parameters
285+
286+
| Parameter | Type | Required | Description |
287+
| ---------------- | ------ | -------- | ------------------------------------------------------------- |
288+
| `action` | string | Yes | Action with current version (e.g., `actions/checkout@v4.0.2`) |
289+
| `target_version` | string | No | Target version (defaults to latest) |
290+
291+
### Example Output
292+
293+
```
294+
# Version Comparison: actions/checkout
295+
296+
From: v4.0.0
297+
To: v4.2.0
298+
299+
## Summary
300+
- Total releases: 3
301+
- Major updates: 0
302+
- Minor updates: 2
303+
- Patch updates: 1
304+
305+
## Release History (chronological)
306+
307+
### v4.1.0 (2025-02-15) - Minor Update
308+
Added support for sparse checkouts and improved performance.
309+
310+
### v4.1.1 (2025-02-20) - Patch Update
311+
Fixed bug with submodule handling on Windows.
312+
313+
### v4.2.0 (2025-03-01) - Minor Update
314+
Added new input parameter for custom checkout paths.
315+
316+
---
317+
318+
Note: Major version updates (marked with ⚠️) may contain breaking changes.
319+
Review the release notes above to understand the impact of each update.
320+
```
321+
251322
## Authentication
252323

253324
The service supports multiple authentication methods, checked in the following

main.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ import {
1919
getLatestInMajorVersion,
2020
suggestUpdates,
2121
} from "./src/tools/suggest-updates.ts";
22+
import {
23+
formatDocumentationResultAsText,
24+
getActionDocumentation,
25+
} from "./src/tools/get-action-documentation.ts";
26+
import {
27+
compareActionVersions,
28+
formatCompareResultAsText,
29+
} from "./src/tools/compare-action-versions.ts";
2230

2331
// Create the MCP server
2432
const server = new McpServer({
@@ -213,6 +221,98 @@ server.tool(
213221
},
214222
);
215223

224+
// Register the get_action_documentation tool
225+
server.tool(
226+
"get_action_documentation",
227+
"Get README documentation for a GitHub Action at a specific version. " +
228+
"Useful for understanding how to use an action at a particular release.",
229+
{
230+
action: z
231+
.string()
232+
.describe(
233+
"Action reference (e.g., 'actions/checkout' or 'actions/checkout@v4')",
234+
),
235+
ref: z
236+
.string()
237+
.optional()
238+
.describe("Optional ref override (tag/branch/commit)"),
239+
},
240+
async ({ action, ref }) => {
241+
try {
242+
const result = await getActionDocumentation({ action, ref });
243+
const text = formatDocumentationResultAsText(result);
244+
245+
return {
246+
content: [
247+
{
248+
type: "text" as const,
249+
text,
250+
},
251+
],
252+
};
253+
} catch (error) {
254+
const message = error instanceof Error
255+
? error.message
256+
: "Unknown error occurred";
257+
return {
258+
content: [
259+
{
260+
type: "text" as const,
261+
text: `Error: ${message}`,
262+
},
263+
],
264+
isError: true,
265+
};
266+
}
267+
},
268+
);
269+
270+
// Register the compare_action_versions tool
271+
server.tool(
272+
"compare_action_versions",
273+
"Compare changes between two versions of a GitHub Action. " +
274+
"Shows release notes and identifies version update levels to help with upgrade decisions.",
275+
{
276+
action: z
277+
.string()
278+
.describe(
279+
"Action with current version (e.g., 'actions/checkout@v4.0.2')",
280+
),
281+
target_version: z
282+
.string()
283+
.optional()
284+
.describe("Target version (defaults to latest)"),
285+
},
286+
async ({ action, target_version }) => {
287+
try {
288+
const result = await compareActionVersions({ action, target_version });
289+
const text = formatCompareResultAsText(result);
290+
291+
return {
292+
content: [
293+
{
294+
type: "text" as const,
295+
text,
296+
},
297+
],
298+
};
299+
} catch (error) {
300+
const message = error instanceof Error
301+
? error.message
302+
: "Unknown error occurred";
303+
return {
304+
content: [
305+
{
306+
type: "text" as const,
307+
text: `Error: ${message}`,
308+
},
309+
],
310+
isError: true,
311+
};
312+
}
313+
},
314+
);
315+
216316
// Start the server with stdio transport
217317
async function main() {
218318
const transport = new StdioServerTransport();

src/github/client.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
GitHubError,
77
GitHubRef,
88
GitHubRelease,
9+
GitHubRepository,
910
GitHubTag,
1011
RateLimitInfo,
1112
} from "./types.ts";
@@ -323,4 +324,72 @@ export class GitHubClient {
323324

324325
return matchingReleases[0] || null;
325326
}
327+
328+
/**
329+
* Get repository information including default branch
330+
*/
331+
async getRepository(owner: string, repo: string): Promise<GitHubRepository> {
332+
const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}`;
333+
return await this.fetch<GitHubRepository>(url);
334+
}
335+
336+
/**
337+
* Get repository default branch name
338+
*/
339+
async getDefaultBranch(owner: string, repo: string): Promise<string> {
340+
const repository = await this.getRepository(owner, repo);
341+
return repository.default_branch;
342+
}
343+
344+
/**
345+
* Get file content from repository at specific ref
346+
* @param owner - Repository owner
347+
* @param repo - Repository name
348+
* @param path - File path (e.g., "README.md")
349+
* @param ref - Branch, tag, or commit SHA (optional, defaults to repo default branch)
350+
*/
351+
async getFileContent(
352+
owner: string,
353+
repo: string,
354+
path: string,
355+
ref?: string,
356+
): Promise<string> {
357+
// Ensure token is resolved before making request
358+
await this.ensureToken();
359+
360+
// Build URL with optional ref parameter
361+
let url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/contents/${path}`;
362+
if (ref) {
363+
url += `?ref=${encodeURIComponent(ref)}`;
364+
}
365+
366+
// Use Accept header to get raw content instead of JSON
367+
const response = await fetch(url, {
368+
headers: {
369+
...this.getHeaders(),
370+
Accept: "application/vnd.github.raw",
371+
},
372+
});
373+
374+
this.updateRateLimitInfo(response);
375+
376+
if (!response.ok) {
377+
const error: GitHubError = await response.json();
378+
if (response.status === 404) {
379+
throw new Error(
380+
`File not found: ${path}${ref ? ` at ref ${ref}` : ""}`,
381+
);
382+
}
383+
if (response.status === 403 && this.rateLimitInfo?.remaining === 0) {
384+
const resetDate = new Date(this.rateLimitInfo.reset * 1000);
385+
throw new Error(
386+
`Rate limit exceeded. Resets at ${resetDate.toISOString()}. ` +
387+
`Consider setting GITHUB_TOKEN for higher limits.`,
388+
);
389+
}
390+
throw new Error(`GitHub API error: ${error.message}`);
391+
}
392+
393+
return response.text();
394+
}
326395
}

src/github/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export interface GitHubRelease {
1212
created_at: string;
1313
published_at: string | null;
1414
html_url: string;
15+
/** Release notes in markdown format */
16+
body?: string | null;
1517
/** Whether or not the release is immutable (protected from modification) */
1618
immutable?: boolean;
1719
assets: GitHubAsset[];
@@ -64,3 +66,18 @@ export interface RateLimitInfo {
6466
reset: number;
6567
used: number;
6668
}
69+
70+
export interface GitHubContent {
71+
name: string;
72+
path: string;
73+
sha: string;
74+
size: number;
75+
type: "file" | "dir";
76+
content?: string; // base64 encoded
77+
encoding?: string; // "base64"
78+
download_url: string | null;
79+
}
80+
81+
export interface GitHubRepository {
82+
default_branch: string;
83+
}

0 commit comments

Comments
 (0)