Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
334892bb20 | ||
|
|
bbe0ccb244 | ||
|
|
ca3b99ea74 | ||
|
|
8a5d2ea4a1 | ||
|
|
112739fb15 | ||
|
|
f95554969e | ||
|
|
9e60aa0a3f | ||
|
|
27350b2a98 | ||
|
|
e8987e92e0 | ||
|
|
2d03946378 | ||
|
|
d061fc5469 | ||
|
|
2d2f67ec42 | ||
|
|
9170087739 | ||
|
|
62db90ab13 | ||
|
|
16f2d5c46b | ||
|
|
95443f8d18 | ||
|
|
48758ceaff | ||
|
|
4b4b2e8afe | ||
|
|
932a853db4 | ||
|
|
e0da58c63f |
6
.github/workflows/check-dist.yml
vendored
6
.github/workflows/check-dist.yml
vendored
@@ -28,11 +28,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: npm
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
- if: ${{ failure() && steps.diff.outcome == 'failure' }}
|
||||
name: Upload Artifact
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -20,11 +20,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: npm
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -30,19 +30,19 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
id: initialize
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
source-root: src
|
||||
|
||||
- name: Autobuild
|
||||
id: autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
id: analyze
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
6
.github/workflows/licensed.yml
vendored
6
.github/workflows/licensed.yml
vendored
@@ -27,11 +27,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
id: setup-node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: npm
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- name: Setup Ruby
|
||||
id: setup-ruby
|
||||
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4
|
||||
uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71
|
||||
with:
|
||||
ruby-version: ruby
|
||||
|
||||
|
||||
2
.github/workflows/linter.yml
vendored
2
.github/workflows/linter.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
39
README.md
39
README.md
@@ -162,8 +162,18 @@ This action now supports **read-only** integration with the GitHub-hosted Model
|
||||
Context Protocol (MCP) server, which provides access to GitHub tools like
|
||||
repository management, issue tracking, and pull request operations.
|
||||
|
||||
> [!NOTE]
|
||||
> The GitHub MCP integration requires a Personal Access Token (PAT) and cannot use the built-in `GITHUB_TOKEN`.
|
||||
#### Authentication
|
||||
|
||||
You can authenticate the MCP server with **either**:
|
||||
|
||||
1. **Personal Access Token (PAT)** – user-scoped token
|
||||
2. **GitHub App Installation Token** (`ghs_…`) – short-lived, app-scoped token
|
||||
> The built-in `GITHUB_TOKEN` is **not** accepted by the MCP server.
|
||||
> Using a **GitHub App installation token** is recommended in most CI environments because it is short-lived and least-privilege by design.
|
||||
|
||||
#### Enabling MCP in the action
|
||||
|
||||
Set `enable-github-mcp: true` and provide a token via `github-mcp-token`.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
@@ -173,7 +183,7 @@ steps:
|
||||
with:
|
||||
prompt: 'List my open pull requests and create a summary'
|
||||
enable-github-mcp: true
|
||||
token: ${{ secrets.USER_PAT }}
|
||||
token: ${{ secrets.USER_PAT }} # or a ghs_ installation token
|
||||
```
|
||||
|
||||
If you want, you can use separate tokens for the AI inference endpoint
|
||||
@@ -188,9 +198,28 @@ steps:
|
||||
prompt: 'List my open pull requests and create a summary'
|
||||
enable-github-mcp: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-mcp-token: ${{ secrets.USER_PAT }}
|
||||
github-mcp-token: ${{ secrets.USER_PAT }} # or a ghs_ installation token
|
||||
```
|
||||
|
||||
#### Configuring GitHub MCP Toolsets
|
||||
|
||||
By default, the GitHub MCP server provides a standard set of tools (`context`, `repos`, `issues`, `pull_requests`, `users`). You can customize which toolsets are available by specifying the `github-mcp-toolsets` parameter:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: AI Inference with Custom Toolsets
|
||||
id: inference
|
||||
uses: actions/ai-inference@v2
|
||||
with:
|
||||
prompt: 'Analyze recent workflow runs and check security alerts'
|
||||
enable-github-mcp: true
|
||||
token: ${{ secrets.USER_PAT }}
|
||||
github-mcp-toolsets: 'repos,issues,pull_requests,actions,code_security'
|
||||
```
|
||||
|
||||
**Available toolsets:**
|
||||
See: [Tool configuration](https://github.com/github/github-mcp-server/blob/main/README.md#tool-configuration)
|
||||
|
||||
When MCP is enabled, the AI model will have access to GitHub tools and can
|
||||
perform actions like searching issues and PRs.
|
||||
|
||||
@@ -212,7 +241,7 @@ the action:
|
||||
| `endpoint` | The endpoint to use for inference. If you're running this as part of an org, you should probably use the org-specific Models endpoint | `https://models.github.ai/inference` |
|
||||
| `max-tokens` | The max number of tokens to generate | 200 |
|
||||
| `enable-github-mcp` | Enable Model Context Protocol integration with GitHub tools | `false` |
|
||||
| `github-mcp-token` | Token to use for GitHub MCP server (defaults to the main token if not specified). This must be a PAT in order for MCP to work | `""` |
|
||||
| `github-mcp-token` | Token to use for GitHub MCP server (defaults to the main token if not specified). | `""` |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ describe('main.ts', () => {
|
||||
|
||||
await run()
|
||||
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token')
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token', '')
|
||||
expect(mockMcpInference).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: [
|
||||
@@ -222,7 +222,7 @@ describe('main.ts', () => {
|
||||
|
||||
await run()
|
||||
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token')
|
||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token', '')
|
||||
expect(mockSimpleInference).toHaveBeenCalled()
|
||||
expect(mockMcpInference).not.toHaveBeenCalled()
|
||||
expect(core.warning).toHaveBeenCalledWith('MCP connection failed, falling back to simple inference')
|
||||
|
||||
@@ -113,6 +113,40 @@ describe('mcp.ts', () => {
|
||||
expect(result?.tools).toHaveLength(0)
|
||||
expect(core.info).toHaveBeenCalledWith('Retrieved 0 tools from GitHub MCP server')
|
||||
})
|
||||
|
||||
it('uses default toolsets when toolsets parameter is not provided', async () => {
|
||||
const token = 'test-token'
|
||||
|
||||
mockConnect.mockResolvedValue(undefined)
|
||||
mockListTools.mockResolvedValue({tools: []})
|
||||
|
||||
await connectToGitHubMCP(token)
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith('Using default GitHub MCP toolsets')
|
||||
})
|
||||
|
||||
it('uses custom toolsets when toolsets parameter is provided', async () => {
|
||||
const token = 'test-token'
|
||||
const toolsets = 'repos,issues,pull_requests,actions'
|
||||
|
||||
mockConnect.mockResolvedValue(undefined)
|
||||
mockListTools.mockResolvedValue({tools: []})
|
||||
|
||||
await connectToGitHubMCP(token, toolsets)
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith('Using GitHub MCP toolsets: repos,issues,pull_requests,actions')
|
||||
})
|
||||
|
||||
it('ignores empty toolsets parameter', async () => {
|
||||
const token = 'test-token'
|
||||
|
||||
mockConnect.mockResolvedValue(undefined)
|
||||
mockListTools.mockResolvedValue({tools: []})
|
||||
|
||||
await connectToGitHubMCP(token, ' ')
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith('Using default GitHub MCP toolsets')
|
||||
})
|
||||
})
|
||||
|
||||
describe('executeToolCall', () => {
|
||||
|
||||
@@ -58,6 +58,10 @@ inputs:
|
||||
description: The token to use for GitHub MCP server (defaults to the main token if not specified). This must be a PAT for MCP to work.
|
||||
required: false
|
||||
default: ''
|
||||
github-mcp-toolsets:
|
||||
description: 'Comma-separated list of toolsets to enable for GitHub MCP (e.g., "repos,issues,pull_requests,actions"). Use "all" for all toolsets, "default" for default set. If not specified, uses default toolsets (context,repos,issues,pull_requests,users).'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
# Define your outputs here.
|
||||
outputs:
|
||||
|
||||
22
dist/index.js
generated
vendored
22
dist/index.js
generated
vendored
@@ -42717,15 +42717,24 @@ class StreamableHTTPClientTransport {
|
||||
/**
|
||||
* Connect to the GitHub MCP server and retrieve available tools
|
||||
*/
|
||||
async function connectToGitHubMCP(token) {
|
||||
async function connectToGitHubMCP(token, toolsets) {
|
||||
const githubMcpUrl = 'https://api.githubcopilot.com/mcp/';
|
||||
coreExports.info('Connecting to GitHub MCP server...');
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'X-MCP-Readonly': 'true',
|
||||
};
|
||||
// Add toolsets header if specified
|
||||
if (toolsets && toolsets.trim() !== '') {
|
||||
headers['X-MCP-Toolsets'] = toolsets;
|
||||
coreExports.info(`Using GitHub MCP toolsets: ${toolsets}`);
|
||||
}
|
||||
else {
|
||||
coreExports.info('Using default GitHub MCP toolsets');
|
||||
}
|
||||
const transport = new StreamableHTTPClientTransport(new URL(githubMcpUrl), {
|
||||
requestInit: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'X-MCP-Readonly': 'true',
|
||||
},
|
||||
headers,
|
||||
},
|
||||
});
|
||||
const client = new Client({
|
||||
@@ -52653,13 +52662,14 @@ async function run() {
|
||||
}
|
||||
// Get GitHub MCP token (use dedicated token if provided, otherwise fall back to main token)
|
||||
const githubMcpToken = coreExports.getInput('github-mcp-token') || token;
|
||||
const githubMcpToolsets = coreExports.getInput('github-mcp-toolsets');
|
||||
const endpoint = coreExports.getInput('endpoint');
|
||||
// Build the inference request with pre-processed messages and response format
|
||||
const inferenceRequest = buildInferenceRequest(promptConfig, systemPrompt, prompt, modelName, promptConfig?.modelParameters?.temperature, promptConfig?.modelParameters?.topP, maxTokens, endpoint, token);
|
||||
const enableMcp = coreExports.getBooleanInput('enable-github-mcp') || false;
|
||||
let modelResponse = null;
|
||||
if (enableMcp) {
|
||||
const mcpClient = await connectToGitHubMCP(githubMcpToken);
|
||||
const mcpClient = await connectToGitHubMCP(githubMcpToken, githubMcpToolsets);
|
||||
if (mcpClient) {
|
||||
modelResponse = await mcpInference(inferenceRequest, mcpClient);
|
||||
}
|
||||
|
||||
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -61,6 +61,7 @@ export async function run(): Promise<void> {
|
||||
|
||||
// Get GitHub MCP token (use dedicated token if provided, otherwise fall back to main token)
|
||||
const githubMcpToken = core.getInput('github-mcp-token') || token
|
||||
const githubMcpToolsets = core.getInput('github-mcp-toolsets')
|
||||
|
||||
const endpoint = core.getInput('endpoint')
|
||||
|
||||
@@ -82,7 +83,7 @@ export async function run(): Promise<void> {
|
||||
let modelResponse: string | null = null
|
||||
|
||||
if (enableMcp) {
|
||||
const mcpClient = await connectToGitHubMCP(githubMcpToken)
|
||||
const mcpClient = await connectToGitHubMCP(githubMcpToken, githubMcpToolsets)
|
||||
|
||||
if (mcpClient) {
|
||||
modelResponse = await mcpInference(inferenceRequest, mcpClient)
|
||||
|
||||
20
src/mcp.ts
20
src/mcp.ts
@@ -35,17 +35,27 @@ export interface GitHubMCPClient {
|
||||
/**
|
||||
* Connect to the GitHub MCP server and retrieve available tools
|
||||
*/
|
||||
export async function connectToGitHubMCP(token: string): Promise<GitHubMCPClient | null> {
|
||||
export async function connectToGitHubMCP(token: string, toolsets?: string): Promise<GitHubMCPClient | null> {
|
||||
const githubMcpUrl = 'https://api.githubcopilot.com/mcp/'
|
||||
|
||||
core.info('Connecting to GitHub MCP server...')
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'X-MCP-Readonly': 'true',
|
||||
}
|
||||
|
||||
// Add toolsets header if specified
|
||||
if (toolsets && toolsets.trim() !== '') {
|
||||
headers['X-MCP-Toolsets'] = toolsets
|
||||
core.info(`Using GitHub MCP toolsets: ${toolsets}`)
|
||||
} else {
|
||||
core.info('Using default GitHub MCP toolsets')
|
||||
}
|
||||
|
||||
const transport = new StreamableHTTPClientTransport(new URL(githubMcpUrl), {
|
||||
requestInit: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'X-MCP-Readonly': 'true',
|
||||
},
|
||||
headers,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user