Sketch out MCP
This commit is contained in:
51
README.md
51
README.md
@@ -80,21 +80,52 @@ steps:
|
||||
cat "${{ steps.inference.outputs.response-file }}"
|
||||
```
|
||||
|
||||
### GitHub MCP Integration (Model Context Protocol)
|
||||
|
||||
This action now supports 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.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: AI Inference with GitHub Tools
|
||||
id: inference
|
||||
uses: actions/ai-inference@v1
|
||||
with:
|
||||
prompt: 'List my open pull requests and create a summary'
|
||||
enable-mcp: true
|
||||
mcp-server-url: 'https://github-mcp-server.fly.dev/mcp' # Optional, this is the default
|
||||
```
|
||||
|
||||
When MCP is enabled, the AI model will have access to GitHub tools and can
|
||||
perform actions like:
|
||||
|
||||
- Listing and managing repositories
|
||||
- Creating, reading, and updating issues
|
||||
- Managing pull requests
|
||||
- Searching code and repositories
|
||||
- And more GitHub operations
|
||||
|
||||
**Note:** MCP integration requires appropriate GitHub permissions for the
|
||||
operations the AI will perform.
|
||||
|
||||
## Inputs
|
||||
|
||||
Various inputs are defined in [`action.yml`](action.yml) to let you configure
|
||||
the action:
|
||||
|
||||
| Name | Description | Default |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| `token` | Token to use for inference. Typically the GITHUB_TOKEN secret | `github.token` |
|
||||
| `prompt` | The prompt to send to the model | N/A |
|
||||
| `prompt-file` | Path to a file containing the prompt. If both `prompt` and `prompt-file` are provided, `prompt-file` takes precedence | `""` |
|
||||
| `system-prompt` | The system prompt to send to the model | `"You are a helpful assistant"` |
|
||||
| `system-prompt-file` | Path to a file containing the system prompt. If both `system-prompt` and `system-prompt-file` are provided, `system-prompt-file` takes precedence | `""` |
|
||||
| `model` | The model to use for inference. Must be available in the [GitHub Models](https://github.com/marketplace?type=models) catalog | `gpt-4o` |
|
||||
| `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 |
|
||||
| Name | Description | Default |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
||||
| `token` | Token to use for inference. Typically the GITHUB_TOKEN secret | `github.token` |
|
||||
| `prompt` | The prompt to send to the model | N/A |
|
||||
| `prompt-file` | Path to a file containing the prompt. If both `prompt` and `prompt-file` are provided, `prompt-file` takes precedence | `""` |
|
||||
| `system-prompt` | The system prompt to send to the model | `"You are a helpful assistant"` |
|
||||
| `system-prompt-file` | Path to a file containing the system prompt. If both `system-prompt` and `system-prompt-file` are provided, `system-prompt-file` takes precedence | `""` |
|
||||
| `model` | The model to use for inference. Must be available in the [GitHub Models](https://github.com/marketplace?type=models) catalog | `gpt-4o` |
|
||||
| `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-mcp` | Enable Model Context Protocol integration with GitHub tools | `false` |
|
||||
| `mcp-server-url` | URL of the MCP server to connect to for GitHub tools | `https://github-mcp-server.fly.dev/mcp` |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
||||
@@ -41,6 +41,14 @@ inputs:
|
||||
description: The token to use
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
enable-mcp:
|
||||
description: Enable Model Context Protocol integration with GitHub tools
|
||||
required: false
|
||||
default: 'false'
|
||||
mcp-server-url:
|
||||
description: URL of the MCP server to connect to
|
||||
required: false
|
||||
default: 'https://github-mcp-server.fly.dev/mcp'
|
||||
|
||||
# Define your outputs here.
|
||||
outputs:
|
||||
|
||||
14872
dist/index.js
generated
vendored
14872
dist/index.js
generated
vendored
File diff suppressed because it is too large
Load Diff
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
694
package-lock.json
generated
694
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -37,12 +37,15 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.11.1"
|
||||
"@actions/core": "^1.11.1",
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"pkce-challenge": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@azure-rest/ai-inference": "latest",
|
||||
"@azure/core-auth": "latest",
|
||||
"@azure/core-sse": "latest",
|
||||
"@azure-rest/ai-inference": "^1.0.0-beta.6",
|
||||
"@azure/core-auth": "^1.10.0",
|
||||
"@azure/core-sse": "^2.3.0",
|
||||
"@eslint/compat": "^1.2.9",
|
||||
"@github/local-action": "^3.2.1",
|
||||
"@jest/globals": "^29.7.0",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import nodeResolve from '@rollup/plugin-node-resolve'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import json from '@rollup/plugin-json'
|
||||
|
||||
const config = {
|
||||
input: 'src/index.ts',
|
||||
@@ -12,7 +13,12 @@ const config = {
|
||||
format: 'es',
|
||||
sourcemap: true
|
||||
},
|
||||
plugins: [typescript(), nodeResolve({ preferBuiltins: true }), commonjs()]
|
||||
plugins: [
|
||||
typescript(),
|
||||
nodeResolve({ preferBuiltins: true }),
|
||||
commonjs(),
|
||||
json()
|
||||
]
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
105
src/main.ts
105
src/main.ts
@@ -1,6 +1,8 @@
|
||||
import * as core from '@actions/core'
|
||||
import ModelClient, { isUnexpected } from '@azure-rest/ai-inference'
|
||||
import { AzureKeyCredential } from '@azure/core-auth'
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
||||
import * as fs from 'fs'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
@@ -56,35 +58,93 @@ export async function run(): Promise<void> {
|
||||
const modelName: string = core.getInput('model')
|
||||
const maxTokens: number = parseInt(core.getInput('max-tokens'), 10)
|
||||
|
||||
const token = core.getInput('token') || process.env['GITHUB_TOKEN']
|
||||
const token = process.env['GITHUB_TOKEN'] || core.getInput('token')
|
||||
if (token === undefined) {
|
||||
throw new Error('GITHUB_TOKEN is not set')
|
||||
}
|
||||
|
||||
const endpoint = core.getInput('endpoint')
|
||||
|
||||
// Get MCP server configuration
|
||||
const mcpServerUrl = 'https://api.githubcopilot.com/mcp/'
|
||||
const enableMcp = core.getBooleanInput('enable-mcp') || false
|
||||
|
||||
let azureTools: any[] = []
|
||||
|
||||
// Connect to MCP server if enabled
|
||||
if (enableMcp || true) {
|
||||
core.info('Connecting to GitHub MCP server...' + token)
|
||||
|
||||
const transport = new StreamableHTTPClientTransport(
|
||||
new URL(mcpServerUrl),
|
||||
{
|
||||
requestInit: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const mcp = new Client({
|
||||
name: 'ai-inference-action',
|
||||
version: '1.0.0',
|
||||
transport
|
||||
})
|
||||
|
||||
try {
|
||||
await mcp.connect(transport)
|
||||
} catch (mcpError) {
|
||||
core.warning(`Failed to connect to MCP server: ${mcpError}`)
|
||||
// Continue without tools if MCP connection fails
|
||||
return
|
||||
}
|
||||
|
||||
core.info('Successfully connected to MCP server')
|
||||
|
||||
// Pull tool metadata
|
||||
const tools = await mcp.listTools()
|
||||
core.info(`Retrieved ${tools.tools?.length || 0} tools from MCP server`)
|
||||
|
||||
// Map MCP → Azure tool definitions
|
||||
azureTools = (tools.tools || []).map((t) => ({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
parameters: t.inputSchema
|
||||
}
|
||||
}))
|
||||
|
||||
core.info(`Mapped ${azureTools.length} tools for Azure AI Inference`)
|
||||
}
|
||||
|
||||
const client = ModelClient(endpoint, new AzureKeyCredential(token), {
|
||||
userAgentOptions: { userAgentPrefix: 'github-actions-ai-inference' }
|
||||
})
|
||||
|
||||
const requestBody: any = {
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: systemPrompt
|
||||
},
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
max_tokens: maxTokens,
|
||||
model: modelName
|
||||
}
|
||||
|
||||
// Add tools if available
|
||||
if (azureTools.length > 0) {
|
||||
requestBody.tools = azureTools
|
||||
}
|
||||
|
||||
const response = await client.path('/chat/completions').post({
|
||||
body: {
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: systemPrompt
|
||||
},
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
max_tokens: maxTokens,
|
||||
model: modelName
|
||||
}
|
||||
body: requestBody
|
||||
})
|
||||
|
||||
if (isUnexpected(response)) {
|
||||
if (response.body.error) {
|
||||
throw response.body.error
|
||||
}
|
||||
throw new Error(
|
||||
'An error occurred while fetching the response (' +
|
||||
response.status +
|
||||
@@ -96,6 +156,21 @@ export async function run(): Promise<void> {
|
||||
const modelResponse: string | null =
|
||||
response.body.choices[0].message.content
|
||||
|
||||
core.info(`Model response: ${response || 'No response content'}`)
|
||||
|
||||
// Handle tool calls if present
|
||||
const toolCalls = response.body.choices[0].message.tool_calls
|
||||
if (toolCalls && toolCalls.length > 0) {
|
||||
core.info(`Model requested ${toolCalls.length} tool calls`)
|
||||
// Note: For now, we'll just log the tool calls
|
||||
// In a full implementation, you'd execute them via MCP and continue the conversation
|
||||
for (const toolCall of toolCalls) {
|
||||
core.info(
|
||||
`Tool call: ${toolCall.function.name} with args: ${toolCall.function.arguments}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Set outputs for other workflow steps to use
|
||||
core.setOutput('response', modelResponse || '')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user