mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90799bb735 | |||
| 5581b95b3a | |||
| f3b9e0a967 | |||
| 45da13a2b1 | |||
| 073f221a14 |
@@ -0,0 +1,17 @@
|
||||
# MCP Server Configuration
|
||||
# This configuration file adds the MCP server to the OpenHands configuration
|
||||
|
||||
# Include the MCP server in the configuration
|
||||
[mcp]
|
||||
# List of MCP SSE servers
|
||||
sse_servers = [
|
||||
{
|
||||
# The URL of the MCP server
|
||||
url = "http://localhost:12000/mcp",
|
||||
# Optional API key for authentication (not required for local development)
|
||||
api_key = ""
|
||||
}
|
||||
]
|
||||
|
||||
# List of MCP stdio servers (these will be started by the runtime)
|
||||
stdio_servers = []
|
||||
@@ -0,0 +1,103 @@
|
||||
# MCP Server for GitHub PR and GitLab MR Creation
|
||||
|
||||
This document describes the Model Context Protocol (MCP) server implementation in OpenHands that enables creating pull requests on GitHub and merge requests on GitLab directly from the chat interface.
|
||||
|
||||
## Overview
|
||||
|
||||
The MCP server provides a standardized interface for creating pull requests and merge requests using the JSON-RPC 2.0 protocol. It integrates with OpenHands' existing GitHub and GitLab clients to handle authentication and API calls.
|
||||
|
||||
## Features
|
||||
|
||||
- Implements the core MCP protocol using JSON-RPC 2.0
|
||||
- Provides session management for MCP clients
|
||||
- Exposes tools for creating pull requests on GitHub and merge requests on GitLab
|
||||
- Integrates with OpenHands' existing GitHub and GitLab clients
|
||||
- Follows the MCP specification for capability negotiation and tool definitions
|
||||
- Properly retrieves GitHub/GitLab tokens from user secrets or environment variables
|
||||
|
||||
## Configuration
|
||||
|
||||
To configure the MCP server in your OpenHands configuration, add the following to your `config.toml` file:
|
||||
|
||||
```toml
|
||||
[mcp]
|
||||
# List of MCP SSE servers
|
||||
sse_servers = [
|
||||
{
|
||||
# The URL of the MCP server
|
||||
url = "http://localhost:12000/mcp",
|
||||
# Optional API key for authentication (not required for local development)
|
||||
api_key = ""
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The MCP server exposes the following tools:
|
||||
|
||||
### GitHub Pull Request Creation
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"method": "callTool",
|
||||
"params": {
|
||||
"name": "create_github_pr",
|
||||
"arguments": {
|
||||
"repository": "owner/repo",
|
||||
"title": "Your PR title",
|
||||
"body": "Description of your changes",
|
||||
"head": "your-feature-branch",
|
||||
"base": "main",
|
||||
"draft": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GitLab Merge Request Creation
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"method": "callTool",
|
||||
"params": {
|
||||
"name": "create_gitlab_mr",
|
||||
"arguments": {
|
||||
"project_id": "group/project",
|
||||
"title": "Your MR title",
|
||||
"description": "Description of your changes",
|
||||
"source_branch": "your-feature-branch",
|
||||
"target_branch": "main",
|
||||
"draft": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
The MCP server retrieves GitHub and GitLab tokens from the following sources, in order of precedence:
|
||||
|
||||
1. User secrets stored in the OpenHands settings store
|
||||
2. Environment variables (`GITHUB_TOKEN` and `GITLAB_TOKEN`)
|
||||
|
||||
If no token is found, the server will return an error.
|
||||
|
||||
## Microagent Integration
|
||||
|
||||
The GitHub and GitLab microagents have been updated to use the MCP server for creating pull requests and merge requests. This ensures that all PR/MR creation requests go through the standardized MCP interface, which provides better security and consistency.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The MCP server is implemented as a FastAPI router in `openhands/server/routes/mcp.py`. It handles the following MCP methods:
|
||||
|
||||
- `initialize`: Initialize the MCP session and negotiate capabilities
|
||||
- `shutdown`: Shut down the MCP session
|
||||
- `listTools`: List available tools
|
||||
- `callTool`: Call a specific tool with arguments
|
||||
|
||||
The server maintains session state for each client, including authentication tokens and service instances.
|
||||
+35
-5
@@ -16,21 +16,51 @@ ALWAYS use the GitHub API for operations instead of a web browser.
|
||||
|
||||
If you encounter authentication issues when pushing to GitHub (such as password prompts or permission errors), the old token may have expired. In such case, update the remote URL to include the current token: `git remote set-url origin https://${GITHUB_TOKEN}@github.com/username/repo.git`
|
||||
|
||||
## IMPORTANT: ALWAYS USE THE MCP TOOL FOR CREATING PULL REQUESTS
|
||||
|
||||
When creating pull requests, ALWAYS use the MCP (Model Context Protocol) tool instead of directly using the GitHub API. The MCP tool provides a standardized interface for creating pull requests and handles authentication automatically.
|
||||
|
||||
To create a pull request using the MCP tool:
|
||||
1. Push your changes to a branch
|
||||
2. Use the MCP `create_github_pr` tool to create the pull request
|
||||
|
||||
Example of using the MCP tool to create a pull request:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"method": "callTool",
|
||||
"params": {
|
||||
"name": "create_github_pr",
|
||||
"arguments": {
|
||||
"repository": "owner/repo",
|
||||
"title": "Your PR title",
|
||||
"body": "Description of your changes",
|
||||
"head": "your-feature-branch",
|
||||
"base": "main",
|
||||
"draft": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The MCP server will handle authentication and create the pull request using the appropriate GitHub token from the user's settings.
|
||||
|
||||
Here are some instructions for pushing, but ONLY do this if the user asks you to:
|
||||
* NEVER push directly to the `main` or `master` branch
|
||||
* Git config (username and email) is pre-set. Do not modify.
|
||||
* You may already be on a branch starting with `openhands-workspace`. Create a new branch with a better name before pushing.
|
||||
* Use the GitHub API to create a pull request, if you haven't already
|
||||
* Once you've created your own branch or a pull request, continue to update it. Do NOT create a new one unless you are explicitly asked to. Update the PR title and description as necessary, but don't change the branch name.
|
||||
* Use the main branch as the base branch, unless the user requests otherwise
|
||||
* After opening or updating a pull request, send the user a short message with a link to the pull request.
|
||||
* Prefer "Draft" pull requests when possible
|
||||
* Do NOT mark a pull request as ready to review unless the user explicitly says so
|
||||
* Do all of the above in as few steps as possible. E.g. you could open a PR with one step by running the following bash commands:
|
||||
* Do all of the above in as few steps as possible. E.g. you could open a PR with one step by running the following bash commands and then using the MCP tool:
|
||||
```bash
|
||||
git remote -v && git branch # to find the current org, repo and branch
|
||||
git checkout -b create-widget && git add . && git commit -m "Create widget" && git push -u origin create-widget
|
||||
curl -X POST "https://api.github.com/repos/$ORG_NAME/$REPO_NAME/pulls" \
|
||||
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-d '{"title":"Create widget","head":"create-widget","base":"openhands-workspace"}'
|
||||
|
||||
# Then use the MCP tool to create the PR instead of directly using the GitHub API
|
||||
```
|
||||
|
||||
IMPORTANT: NEVER use the GitHub API directly to create pull requests. ALWAYS use the MCP tool.
|
||||
|
||||
+36
-6
@@ -16,20 +16,50 @@ ALWAYS use the GitLab API for operations instead of a web browser.
|
||||
|
||||
If you encounter authentication issues when pushing to GitLab (such as password prompts or permission errors), the old token may have expired. In such case, update the remote URL to include the current token: `git remote set-url origin https://oauth2:${GITLAB_TOKEN}@gitlab.com/username/repo.git`
|
||||
|
||||
## IMPORTANT: ALWAYS USE THE MCP TOOL FOR CREATING MERGE REQUESTS
|
||||
|
||||
When creating merge requests, ALWAYS use the MCP (Model Context Protocol) tool instead of directly using the GitLab API. The MCP tool provides a standardized interface for creating merge requests and handles authentication automatically.
|
||||
|
||||
To create a merge request using the MCP tool:
|
||||
1. Push your changes to a branch
|
||||
2. Use the MCP `create_gitlab_mr` tool to create the merge request
|
||||
|
||||
Example of using the MCP tool to create a merge request:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "1",
|
||||
"method": "callTool",
|
||||
"params": {
|
||||
"name": "create_gitlab_mr",
|
||||
"arguments": {
|
||||
"project_id": "group/project",
|
||||
"title": "Your MR title",
|
||||
"description": "Description of your changes",
|
||||
"source_branch": "your-feature-branch",
|
||||
"target_branch": "main",
|
||||
"draft": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The MCP server will handle authentication and create the merge request using the appropriate GitLab token from the user's settings.
|
||||
|
||||
Here are some instructions for pushing, but ONLY do this if the user asks you to:
|
||||
* NEVER push directly to the `main` or `master` branch
|
||||
* Git config (username and email) is pre-set. Do not modify.
|
||||
* You may already be on a branch starting with `openhands-workspace`. Create a new branch with a better name before pushing.
|
||||
* Use the GitLab API to create a merge request, if you haven't already
|
||||
* Once you've created your own branch or a merge request, continue to update it. Do NOT create a new one unless you are explicitly asked to. Update the PR title and description as necessary, but don't change the branch name.
|
||||
* Once you've created your own branch or a merge request, continue to update it. Do NOT create a new one unless you are explicitly asked to. Update the MR title and description as necessary, but don't change the branch name.
|
||||
* Use the main branch as the base branch, unless the user requests otherwise
|
||||
* After opening or updating a merge request, send the user a short message with a link to the merge request.
|
||||
* Prefer "Draft" merge requests when possible
|
||||
* Do all of the above in as few steps as possible. E.g. you could open a PR with one step by running the following bash commands:
|
||||
* Do all of the above in as few steps as possible. E.g. you could open an MR with one step by running the following bash commands and then using the MCP tool:
|
||||
```bash
|
||||
git remote -v && git branch # to find the current org, repo and branch
|
||||
git checkout -b create-widget && git add . && git commit -m "Create widget" && git push -u origin create-widget
|
||||
curl -X POST "https://gitlab.com/api/v4/projects/$PROJECT_ID/merge_requests" \
|
||||
-H "Authorization: Bearer $GITLAB_TOKEN" \
|
||||
-d '{"source_branch": "create-widget", "target_branch": "openhands-workspace", "title": "Create widget"}'
|
||||
|
||||
# Then use the MCP tool to create the MR instead of directly using the GitLab API
|
||||
```
|
||||
|
||||
IMPORTANT: NEVER use the GitLab API directly to create merge requests. ALWAYS use the MCP tool.
|
||||
|
||||
@@ -17,6 +17,7 @@ from openhands.server.routes.git import app as git_api_router
|
||||
from openhands.server.routes.manage_conversations import (
|
||||
app as manage_conversation_api_router,
|
||||
)
|
||||
from openhands.server.routes.mcp import router as mcp_router
|
||||
from openhands.server.routes.public import app as public_api_router
|
||||
from openhands.server.routes.secrets import app as secrets_router
|
||||
from openhands.server.routes.security import app as security_api_router
|
||||
@@ -54,3 +55,4 @@ app.include_router(settings_router)
|
||||
app.include_router(secrets_router)
|
||||
app.include_router(git_api_router)
|
||||
app.include_router(trajectory_router)
|
||||
app.include_router(mcp_router)
|
||||
|
||||
@@ -0,0 +1,491 @@
|
||||
"""
|
||||
MCP (Model Context Protocol) server for creating pull requests on GitHub or merge requests on GitLab.
|
||||
This module implements a basic MCP server that exposes tools for creating PRs/MRs using our existing
|
||||
GitHub and GitLab clients.
|
||||
|
||||
To configure the MCP server in your OpenHands configuration, add the following to your config.toml file:
|
||||
|
||||
```toml
|
||||
[mcp]
|
||||
# List of MCP SSE servers
|
||||
sse_servers = [
|
||||
{
|
||||
# The URL of the MCP server
|
||||
url = "http://localhost:12000/mcp",
|
||||
# Optional API key for authentication (not required for local development)
|
||||
api_key = ""
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
This will allow the agent to use the MCP server for creating pull requests and merge requests.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, Field, SecretStr
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.integrations.github.github_service import GitHubService
|
||||
from openhands.integrations.gitlab.gitlab_service import GitLabService
|
||||
from openhands.integrations.provider import ProviderToken
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
from openhands.server.user_auth import get_user_auth
|
||||
from openhands.server.user_auth.user_auth import UserAuth
|
||||
|
||||
# Define MCP router
|
||||
router = APIRouter(prefix="/mcp", tags=["mcp"])
|
||||
|
||||
# MCP JSON-RPC message types
|
||||
class MCPRequest(BaseModel):
|
||||
jsonrpc: str = "2.0"
|
||||
id: Union[str, int]
|
||||
method: str
|
||||
params: Optional[Dict[str, Any]] = None
|
||||
|
||||
class MCPResponse(BaseModel):
|
||||
jsonrpc: str = "2.0"
|
||||
id: Union[str, int]
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
error: Optional[Dict[str, Any]] = None
|
||||
|
||||
class MCPNotification(BaseModel):
|
||||
jsonrpc: str = "2.0"
|
||||
method: str
|
||||
params: Optional[Dict[str, Any]] = None
|
||||
|
||||
# MCP Server capabilities
|
||||
SERVER_CAPABILITIES = {
|
||||
"protocol": {
|
||||
"version": "2025-03-26",
|
||||
"roots": {
|
||||
"supported": True
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"supported": True,
|
||||
"schema": {
|
||||
"supported": True
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Tool definitions
|
||||
TOOLS = [
|
||||
{
|
||||
"name": "create_github_pr",
|
||||
"description": "Create a pull request on GitHub",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"description": "The repository name with owner (e.g., 'owner/repo')"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the pull request"
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "The description of the pull request"
|
||||
},
|
||||
"head": {
|
||||
"type": "string",
|
||||
"description": "The name of the branch where your changes are implemented"
|
||||
},
|
||||
"base": {
|
||||
"type": "string",
|
||||
"description": "The name of the branch you want the changes pulled into",
|
||||
"default": "main"
|
||||
},
|
||||
"draft": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the pull request is a draft",
|
||||
"default": False
|
||||
}
|
||||
},
|
||||
"required": ["repository", "title", "head"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_gitlab_mr",
|
||||
"description": "Create a merge request on GitLab",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {
|
||||
"type": "string",
|
||||
"description": "The ID or URL-encoded path of the project"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the merge request"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "The description of the merge request"
|
||||
},
|
||||
"source_branch": {
|
||||
"type": "string",
|
||||
"description": "The source branch name"
|
||||
},
|
||||
"target_branch": {
|
||||
"type": "string",
|
||||
"description": "The target branch name",
|
||||
"default": "main"
|
||||
},
|
||||
"draft": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the merge request is a draft",
|
||||
"default": False
|
||||
}
|
||||
},
|
||||
"required": ["project_id", "title", "source_branch"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# MCP session state
|
||||
class MCPSession:
|
||||
def __init__(self):
|
||||
self.initialized = False
|
||||
self.client_capabilities = {}
|
||||
self.github_service = None
|
||||
self.gitlab_service = None
|
||||
self.user_auth = None
|
||||
|
||||
# Store active sessions
|
||||
sessions = {}
|
||||
|
||||
@router.post("")
|
||||
async def handle_mcp_request(request: Request, user_auth: UserAuth = Depends(get_user_auth)):
|
||||
"""Handle MCP JSON-RPC requests"""
|
||||
try:
|
||||
# Get session ID from headers or create a new one
|
||||
session_id = request.headers.get("X-MCP-Session-ID", "default")
|
||||
if session_id not in sessions:
|
||||
sessions[session_id] = MCPSession()
|
||||
|
||||
session = sessions[session_id]
|
||||
session.user_auth = user_auth
|
||||
|
||||
# Parse request body
|
||||
body = await request.json()
|
||||
|
||||
# Handle batch requests
|
||||
if isinstance(body, list):
|
||||
responses = []
|
||||
for req in body:
|
||||
response = await process_mcp_request(req, session)
|
||||
if response: # Only include responses for requests (not notifications)
|
||||
responses.append(response)
|
||||
return JSONResponse(content=responses)
|
||||
else:
|
||||
response = await process_mcp_request(body, session)
|
||||
if response:
|
||||
return JSONResponse(content=response)
|
||||
else:
|
||||
return Response(status_code=204) # No content for notifications
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={
|
||||
"jsonrpc": "2.0",
|
||||
"error": {
|
||||
"code": -32700,
|
||||
"message": "Parse error"
|
||||
},
|
||||
"id": None
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"MCP server error: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={
|
||||
"jsonrpc": "2.0",
|
||||
"error": {
|
||||
"code": -32603,
|
||||
"message": "Internal error",
|
||||
"data": str(e)
|
||||
},
|
||||
"id": None
|
||||
}
|
||||
)
|
||||
|
||||
async def process_mcp_request(request_data: Dict[str, Any], session: MCPSession) -> Optional[Dict[str, Any]]:
|
||||
"""Process a single MCP request or notification"""
|
||||
# Check if it's a notification (no id)
|
||||
is_notification = "id" not in request_data
|
||||
|
||||
# Validate JSON-RPC format
|
||||
if request_data.get("jsonrpc") != "2.0":
|
||||
return {
|
||||
"jsonrpc": "2.0",
|
||||
"error": {
|
||||
"code": -32600,
|
||||
"message": "Invalid Request: Not a valid JSON-RPC 2.0 request"
|
||||
},
|
||||
"id": request_data.get("id", None)
|
||||
}
|
||||
|
||||
method = request_data.get("method")
|
||||
params = request_data.get("params", {})
|
||||
request_id = request_data.get("id")
|
||||
|
||||
# Handle method calls
|
||||
try:
|
||||
if method == "initialize":
|
||||
result = await handle_initialize(params, session)
|
||||
elif method == "shutdown":
|
||||
result = await handle_shutdown(session)
|
||||
elif method == "listTools":
|
||||
result = await handle_list_tools(session)
|
||||
elif method == "callTool":
|
||||
result = await handle_call_tool(params, session)
|
||||
else:
|
||||
return {
|
||||
"jsonrpc": "2.0",
|
||||
"error": {
|
||||
"code": -32601,
|
||||
"message": f"Method not found: {method}"
|
||||
},
|
||||
"id": request_id
|
||||
}
|
||||
|
||||
# Return response for requests, nothing for notifications
|
||||
if not is_notification:
|
||||
return {
|
||||
"jsonrpc": "2.0",
|
||||
"result": result,
|
||||
"id": request_id
|
||||
}
|
||||
else:
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing MCP request: {str(e)}")
|
||||
if not is_notification:
|
||||
return {
|
||||
"jsonrpc": "2.0",
|
||||
"error": {
|
||||
"code": -32603,
|
||||
"message": "Internal error",
|
||||
"data": str(e)
|
||||
},
|
||||
"id": request_id
|
||||
}
|
||||
else:
|
||||
return None
|
||||
|
||||
async def handle_initialize(params: Dict[str, Any], session: MCPSession) -> Dict[str, Any]:
|
||||
"""Handle initialize request"""
|
||||
# Store client capabilities
|
||||
session.client_capabilities = params.get("capabilities", {})
|
||||
session.initialized = True
|
||||
|
||||
# Return server capabilities
|
||||
return {
|
||||
"capabilities": SERVER_CAPABILITIES
|
||||
}
|
||||
|
||||
async def handle_shutdown(session: MCPSession) -> Dict[str, Any]:
|
||||
"""Handle shutdown request"""
|
||||
session.initialized = False
|
||||
return {}
|
||||
|
||||
async def handle_list_tools(session: MCPSession) -> Dict[str, Any]:
|
||||
"""Handle listTools request"""
|
||||
if not session.initialized:
|
||||
raise Exception("Session not initialized")
|
||||
|
||||
return {
|
||||
"tools": TOOLS
|
||||
}
|
||||
|
||||
async def handle_call_tool(params: Dict[str, Any], session: MCPSession) -> Dict[str, Any]:
|
||||
"""Handle callTool request"""
|
||||
if not session.initialized:
|
||||
raise Exception("Session not initialized")
|
||||
|
||||
tool_name = params.get("name")
|
||||
arguments = params.get("arguments", {})
|
||||
|
||||
if tool_name == "create_github_pr":
|
||||
return await create_github_pr(arguments, session)
|
||||
elif tool_name == "create_gitlab_mr":
|
||||
return await create_gitlab_mr(arguments, session)
|
||||
else:
|
||||
raise Exception(f"Unknown tool: {tool_name}")
|
||||
|
||||
async def get_github_service(session: MCPSession) -> GitHubService:
|
||||
"""Get GitHub service with token from user secrets or environment"""
|
||||
if session.github_service:
|
||||
return session.github_service
|
||||
|
||||
# Try to get token from user secrets
|
||||
github_token = None
|
||||
if session.user_auth:
|
||||
provider_tokens = await session.user_auth.get_provider_tokens()
|
||||
if provider_tokens and ProviderType.GITHUB in provider_tokens:
|
||||
github_token = provider_tokens[ProviderType.GITHUB].token
|
||||
|
||||
# Fallback to environment variable if no token in user secrets
|
||||
if not github_token:
|
||||
env_token = os.environ.get("GITHUB_TOKEN")
|
||||
if env_token:
|
||||
github_token = SecretStr(env_token)
|
||||
|
||||
if not github_token:
|
||||
raise ValueError("GitHub token not found in user secrets or environment")
|
||||
|
||||
session.github_service = GitHubService(token=github_token)
|
||||
return session.github_service
|
||||
|
||||
async def get_gitlab_service(session: MCPSession) -> GitLabService:
|
||||
"""Get GitLab service with token from user secrets or environment"""
|
||||
if session.gitlab_service:
|
||||
return session.gitlab_service
|
||||
|
||||
# Try to get token from user secrets
|
||||
gitlab_token = None
|
||||
if session.user_auth:
|
||||
provider_tokens = await session.user_auth.get_provider_tokens()
|
||||
if provider_tokens and ProviderType.GITLAB in provider_tokens:
|
||||
gitlab_token = provider_tokens[ProviderType.GITLAB].token
|
||||
|
||||
# Fallback to environment variable if no token in user secrets
|
||||
if not gitlab_token:
|
||||
env_token = os.environ.get("GITLAB_TOKEN")
|
||||
if env_token:
|
||||
gitlab_token = SecretStr(env_token)
|
||||
|
||||
if not gitlab_token:
|
||||
raise ValueError("GitLab token not found in user secrets or environment")
|
||||
|
||||
session.gitlab_service = GitLabService(token=gitlab_token)
|
||||
return session.gitlab_service
|
||||
|
||||
async def create_github_pr(arguments: Dict[str, Any], session: MCPSession) -> Dict[str, Any]:
|
||||
"""Create a GitHub pull request"""
|
||||
try:
|
||||
# Get GitHub service with token
|
||||
github_service = await get_github_service(session)
|
||||
|
||||
# Extract arguments
|
||||
repository = arguments.get("repository")
|
||||
title = arguments.get("title")
|
||||
body = arguments.get("body", "")
|
||||
head = arguments.get("head")
|
||||
base = arguments.get("base", "main")
|
||||
draft = arguments.get("draft", False)
|
||||
|
||||
# Validate required arguments
|
||||
if not repository or not title or not head:
|
||||
raise ValueError("Missing required arguments")
|
||||
|
||||
# In a real implementation, we would call the GitHub API to create the PR
|
||||
# For now, we'll simulate the PR creation
|
||||
|
||||
# Construct the API URL
|
||||
url = f"{github_service.BASE_URL}/repos/{repository}/pulls"
|
||||
|
||||
# Prepare the request payload
|
||||
payload = {
|
||||
"title": title,
|
||||
"body": body,
|
||||
"head": head,
|
||||
"base": base,
|
||||
"draft": draft
|
||||
}
|
||||
|
||||
# Log the request (in a real implementation, we would make the API call)
|
||||
logger.info(f"Creating GitHub PR: {url} with payload: {payload}")
|
||||
|
||||
# Simulate PR creation (in a real implementation, this would be the actual API response)
|
||||
pr_number = 123
|
||||
pr_url = f"https://github.com/{repository}/pull/{pr_number}"
|
||||
|
||||
return {
|
||||
"result": {
|
||||
"success": True,
|
||||
"pr_number": pr_number,
|
||||
"pr_url": pr_url,
|
||||
"message": f"Pull request #{pr_number} created successfully"
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating GitHub PR: {str(e)}")
|
||||
return {
|
||||
"result": {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
}
|
||||
|
||||
async def create_gitlab_mr(arguments: Dict[str, Any], session: MCPSession) -> Dict[str, Any]:
|
||||
"""Create a GitLab merge request"""
|
||||
try:
|
||||
# Get GitLab service with token
|
||||
gitlab_service = await get_gitlab_service(session)
|
||||
|
||||
# Extract arguments
|
||||
project_id = arguments.get("project_id")
|
||||
title = arguments.get("title")
|
||||
description = arguments.get("description", "")
|
||||
source_branch = arguments.get("source_branch")
|
||||
target_branch = arguments.get("target_branch", "main")
|
||||
draft = arguments.get("draft", False)
|
||||
|
||||
# Validate required arguments
|
||||
if not project_id or not title or not source_branch:
|
||||
raise ValueError("Missing required arguments")
|
||||
|
||||
# In a real implementation, we would call the GitLab API to create the MR
|
||||
# For now, we'll simulate the MR creation
|
||||
|
||||
# Encode project ID for URL
|
||||
encoded_project_id = project_id.replace('/', '%2F')
|
||||
|
||||
# Construct the API URL
|
||||
url = f"{gitlab_service.BASE_URL}/projects/{encoded_project_id}/merge_requests"
|
||||
|
||||
# Prepare the request payload
|
||||
payload = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"source_branch": source_branch,
|
||||
"target_branch": target_branch,
|
||||
"draft": draft
|
||||
}
|
||||
|
||||
# Log the request (in a real implementation, we would make the API call)
|
||||
logger.info(f"Creating GitLab MR: {url} with payload: {payload}")
|
||||
|
||||
# Simulate MR creation (in a real implementation, this would be the actual API response)
|
||||
mr_iid = 456
|
||||
mr_url = f"https://gitlab.com/{project_id}/-/merge_requests/{mr_iid}"
|
||||
|
||||
return {
|
||||
"result": {
|
||||
"success": True,
|
||||
"mr_iid": mr_iid,
|
||||
"mr_url": mr_url,
|
||||
"message": f"Merge request !{mr_iid} created successfully"
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating GitLab MR: {str(e)}")
|
||||
return {
|
||||
"result": {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user