Merge pull request #142 from maartenvandiemen/feature/pass-toolsets
Pass GitHub MCP Tools
This commit is contained in:
@@ -28,11 +28,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
id: setup-node
|
id: setup-node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: npm
|
cache: npm
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
- if: ${{ failure() && steps.diff.outcome == 'failure' }}
|
- if: ${{ failure() && steps.diff.outcome == 'failure' }}
|
||||||
name: Upload Artifact
|
name: Upload Artifact
|
||||||
id: upload
|
id: upload
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: dist/
|
path: dist/
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
id: setup-node
|
id: setup-node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: npm
|
cache: npm
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
|||||||
@@ -30,19 +30,19 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
id: initialize
|
id: initialize
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
source-root: src
|
source-root: src
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
id: autobuild
|
id: autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@v4
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
id: analyze
|
id: analyze
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v4
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
id: setup-node
|
id: setup-node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version-file: .node-version
|
node-version-file: .node-version
|
||||||
cache: npm
|
cache: npm
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
id: setup-ruby
|
id: setup-ruby
|
||||||
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4
|
uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71
|
||||||
with:
|
with:
|
||||||
ruby-version: ruby
|
ruby-version: ruby
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,25 @@ steps:
|
|||||||
github-mcp-token: ${{ secrets.USER_PAT }} # or a ghs_ installation token
|
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
|
When MCP is enabled, the AI model will have access to GitHub tools and can
|
||||||
perform actions like searching issues and PRs.
|
perform actions like searching issues and PRs.
|
||||||
|
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ describe('main.ts', () => {
|
|||||||
|
|
||||||
await run()
|
await run()
|
||||||
|
|
||||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token')
|
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token', '')
|
||||||
expect(mockMcpInference).toHaveBeenCalledWith(
|
expect(mockMcpInference).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
messages: [
|
messages: [
|
||||||
@@ -222,7 +222,7 @@ describe('main.ts', () => {
|
|||||||
|
|
||||||
await run()
|
await run()
|
||||||
|
|
||||||
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token')
|
expect(mockConnectToGitHubMCP).toHaveBeenCalledWith('fake-token', '')
|
||||||
expect(mockSimpleInference).toHaveBeenCalled()
|
expect(mockSimpleInference).toHaveBeenCalled()
|
||||||
expect(mockMcpInference).not.toHaveBeenCalled()
|
expect(mockMcpInference).not.toHaveBeenCalled()
|
||||||
expect(core.warning).toHaveBeenCalledWith('MCP connection failed, falling back to simple inference')
|
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(result?.tools).toHaveLength(0)
|
||||||
expect(core.info).toHaveBeenCalledWith('Retrieved 0 tools from GitHub MCP server')
|
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', () => {
|
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.
|
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
|
required: false
|
||||||
default: ''
|
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.
|
# Define your outputs here.
|
||||||
outputs:
|
outputs:
|
||||||
|
|||||||
+16
-6
@@ -42717,15 +42717,24 @@ class StreamableHTTPClientTransport {
|
|||||||
/**
|
/**
|
||||||
* Connect to the GitHub MCP server and retrieve available tools
|
* 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/';
|
const githubMcpUrl = 'https://api.githubcopilot.com/mcp/';
|
||||||
coreExports.info('Connecting to GitHub MCP server...');
|
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), {
|
const transport = new StreamableHTTPClientTransport(new URL(githubMcpUrl), {
|
||||||
requestInit: {
|
requestInit: {
|
||||||
headers: {
|
headers,
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
'X-MCP-Readonly': 'true',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const client = new Client({
|
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)
|
// Get GitHub MCP token (use dedicated token if provided, otherwise fall back to main token)
|
||||||
const githubMcpToken = coreExports.getInput('github-mcp-token') || token;
|
const githubMcpToken = coreExports.getInput('github-mcp-token') || token;
|
||||||
|
const githubMcpToolsets = coreExports.getInput('github-mcp-toolsets');
|
||||||
const endpoint = coreExports.getInput('endpoint');
|
const endpoint = coreExports.getInput('endpoint');
|
||||||
// Build the inference request with pre-processed messages and response format
|
// 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 inferenceRequest = buildInferenceRequest(promptConfig, systemPrompt, prompt, modelName, promptConfig?.modelParameters?.temperature, promptConfig?.modelParameters?.topP, maxTokens, endpoint, token);
|
||||||
const enableMcp = coreExports.getBooleanInput('enable-github-mcp') || false;
|
const enableMcp = coreExports.getBooleanInput('enable-github-mcp') || false;
|
||||||
let modelResponse = null;
|
let modelResponse = null;
|
||||||
if (enableMcp) {
|
if (enableMcp) {
|
||||||
const mcpClient = await connectToGitHubMCP(githubMcpToken);
|
const mcpClient = await connectToGitHubMCP(githubMcpToken, githubMcpToolsets);
|
||||||
if (mcpClient) {
|
if (mcpClient) {
|
||||||
modelResponse = await mcpInference(inferenceRequest, mcpClient);
|
modelResponse = await mcpInference(inferenceRequest, mcpClient);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+2
-1
@@ -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)
|
// Get GitHub MCP token (use dedicated token if provided, otherwise fall back to main token)
|
||||||
const githubMcpToken = core.getInput('github-mcp-token') || token
|
const githubMcpToken = core.getInput('github-mcp-token') || token
|
||||||
|
const githubMcpToolsets = core.getInput('github-mcp-toolsets')
|
||||||
|
|
||||||
const endpoint = core.getInput('endpoint')
|
const endpoint = core.getInput('endpoint')
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ export async function run(): Promise<void> {
|
|||||||
let modelResponse: string | null = null
|
let modelResponse: string | null = null
|
||||||
|
|
||||||
if (enableMcp) {
|
if (enableMcp) {
|
||||||
const mcpClient = await connectToGitHubMCP(githubMcpToken)
|
const mcpClient = await connectToGitHubMCP(githubMcpToken, githubMcpToolsets)
|
||||||
|
|
||||||
if (mcpClient) {
|
if (mcpClient) {
|
||||||
modelResponse = await mcpInference(inferenceRequest, mcpClient)
|
modelResponse = await mcpInference(inferenceRequest, mcpClient)
|
||||||
|
|||||||
+15
-5
@@ -35,17 +35,27 @@ export interface GitHubMCPClient {
|
|||||||
/**
|
/**
|
||||||
* Connect to the GitHub MCP server and retrieve available tools
|
* 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/'
|
const githubMcpUrl = 'https://api.githubcopilot.com/mcp/'
|
||||||
|
|
||||||
core.info('Connecting to GitHub MCP server...')
|
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), {
|
const transport = new StreamableHTTPClientTransport(new URL(githubMcpUrl), {
|
||||||
requestInit: {
|
requestInit: {
|
||||||
headers: {
|
headers,
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
'X-MCP-Readonly': 'true',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user