Add Claude Code integration with SSE support
- Add Server-Sent Events (SSE) endpoint for Claude Code MCP integration - Create MCP configuration for Claude Code CLI - Update tool configuration to support modern OpenAPI format - Add documentation for Claude Code integration options - Create CLAUDE.md guide for AI coding agents 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
27
CLAUDE.md
Normal file
27
CLAUDE.md
Normal file
@ -0,0 +1,27 @@
|
||||
# CLAUDE.md - Guide for AI Coding Agents
|
||||
|
||||
## Project Overview
|
||||
Nomad MCP is a service that enables management of HashiCorp Nomad jobs via REST API, with Claude AI integration.
|
||||
|
||||
## Commands
|
||||
- **Run server**: `uvicorn app.main:app --reload --host 0.0.0.0 --port 8000`
|
||||
- **Tests**: `pytest` (all) or `pytest tests/test_nomad_service.py::test_job_lifecycle` (single)
|
||||
- **Build docker**: `docker build -t nomad-mcp .`
|
||||
- **Run docker**: `docker-compose up -d`
|
||||
|
||||
## Code Style
|
||||
- **Imports**: Standard library → Third-party → Local modules (alphabetically)
|
||||
- **Type annotations**: Required for all function parameters and returns
|
||||
- **Error handling**: Use try/except with proper logging and HTTP exceptions
|
||||
- **Logging**: Use Python's logging module with appropriate levels
|
||||
- **API responses**: Return consistent JSON structures with Pydantic models
|
||||
- **Docstrings**: Required for all functions and classes
|
||||
- **Variables**: snake_case for variables, CamelCase for classes
|
||||
|
||||
## Structure
|
||||
- `/app`: Main code (/routers, /schemas, /services)
|
||||
- `/configs`: Configuration files
|
||||
- `/static`: Frontend assets
|
||||
- `/tests`: Test files
|
||||
|
||||
Always maintain backward compatibility with existing API endpoints. Follow REST principles.
|
@ -1,249 +1,295 @@
|
||||
# Claude Integration with Nomad MCP
|
||||
|
||||
This document explains how to configure Claude to connect to the Nomad MCP service and manage jobs.
|
||||
|
||||
## Overview
|
||||
|
||||
The Nomad MCP service provides a simplified REST API specifically designed for Claude to interact with Nomad jobs. This API allows Claude to:
|
||||
|
||||
1. List all jobs in a namespace
|
||||
2. Get the status of a specific job
|
||||
3. Start, stop, and restart jobs
|
||||
4. Create new jobs with a simplified specification
|
||||
5. Retrieve logs from jobs
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The Claude-specific API is available at the `/api/claude` prefix. The following endpoints are available:
|
||||
|
||||
### List Jobs
|
||||
|
||||
```
|
||||
GET /api/claude/list-jobs?namespace=development
|
||||
```
|
||||
|
||||
Returns a list of all jobs in the specified namespace with their IDs, names, statuses, and types.
|
||||
|
||||
### Manage Jobs
|
||||
|
||||
```
|
||||
POST /api/claude/jobs
|
||||
```
|
||||
|
||||
Manages existing jobs with operations like status check, stop, and restart.
|
||||
|
||||
Request body:
|
||||
```json
|
||||
{
|
||||
"job_id": "example-job",
|
||||
"action": "status|stop|restart",
|
||||
"namespace": "development",
|
||||
"purge": false
|
||||
}
|
||||
```
|
||||
|
||||
### Create Jobs
|
||||
|
||||
```
|
||||
POST /api/claude/create-job
|
||||
```
|
||||
|
||||
Creates a new job with a simplified specification.
|
||||
|
||||
Request body:
|
||||
```json
|
||||
{
|
||||
"job_id": "example-job",
|
||||
"name": "Example Job",
|
||||
"type": "service",
|
||||
"datacenters": ["jm"],
|
||||
"namespace": "development",
|
||||
"docker_image": "nginx:latest",
|
||||
"count": 1,
|
||||
"cpu": 100,
|
||||
"memory": 128,
|
||||
"ports": [
|
||||
{
|
||||
"Label": "http",
|
||||
"Value": 0,
|
||||
"To": 80
|
||||
}
|
||||
],
|
||||
"env_vars": {
|
||||
"ENV_VAR1": "value1",
|
||||
"ENV_VAR2": "value2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Job Logs
|
||||
|
||||
```
|
||||
GET /api/claude/job-logs/{job_id}?namespace=development
|
||||
```
|
||||
|
||||
Retrieves logs from the latest allocation of the specified job.
|
||||
|
||||
## Configuring Claude Desktop Application
|
||||
|
||||
To configure Claude to connect to the Nomad MCP service, follow these steps:
|
||||
|
||||
### 1. Set Up API Access
|
||||
|
||||
Claude needs to be configured with the base URL of your Nomad MCP service. This is typically:
|
||||
|
||||
```
|
||||
http://your-server-address:8000
|
||||
```
|
||||
|
||||
### 2. Create a Claude Tool Configuration
|
||||
|
||||
In the Claude desktop application, you can create a custom tool configuration that allows Claude to interact with the Nomad MCP API. Here's a sample configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"name": "nomad_mcp",
|
||||
"description": "Manage Nomad jobs through the MCP service",
|
||||
"api_endpoints": [
|
||||
{
|
||||
"name": "list_jobs",
|
||||
"description": "List all jobs in a namespace",
|
||||
"method": "GET",
|
||||
"url": "http://your-server-address:8000/api/claude/list-jobs",
|
||||
"params": [
|
||||
{
|
||||
"name": "namespace",
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"default": "development"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "manage_job",
|
||||
"description": "Manage a job (status, stop, restart)",
|
||||
"method": "POST",
|
||||
"url": "http://your-server-address:8000/api/claude/jobs",
|
||||
"body": {
|
||||
"job_id": "string",
|
||||
"action": "string",
|
||||
"namespace": "string",
|
||||
"purge": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_job",
|
||||
"description": "Create a new job",
|
||||
"method": "POST",
|
||||
"url": "http://your-server-address:8000/api/claude/create-job",
|
||||
"body": {
|
||||
"job_id": "string",
|
||||
"name": "string",
|
||||
"type": "string",
|
||||
"datacenters": "array",
|
||||
"namespace": "string",
|
||||
"docker_image": "string",
|
||||
"count": "integer",
|
||||
"cpu": "integer",
|
||||
"memory": "integer",
|
||||
"ports": "array",
|
||||
"env_vars": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_job_logs",
|
||||
"description": "Get logs for a job",
|
||||
"method": "GET",
|
||||
"url": "http://your-server-address:8000/api/claude/job-logs/{job_id}",
|
||||
"params": [
|
||||
{
|
||||
"name": "namespace",
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"default": "development"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Import the Tool Configuration
|
||||
|
||||
1. Open the Claude desktop application
|
||||
2. Go to Settings > Tools
|
||||
3. Click "Import Tool Configuration"
|
||||
4. Select the JSON file with the above configuration
|
||||
5. Click "Save"
|
||||
|
||||
### 4. Test the Connection
|
||||
|
||||
You can test the connection by asking Claude to list all jobs:
|
||||
|
||||
```
|
||||
Please list all jobs in the development namespace using the Nomad MCP service.
|
||||
```
|
||||
|
||||
Claude should use the configured tool to make an API request to the Nomad MCP service and return the list of jobs.
|
||||
|
||||
## Example Prompts for Claude
|
||||
|
||||
Here are some example prompts you can use with Claude to interact with the Nomad MCP service:
|
||||
|
||||
### List Jobs
|
||||
|
||||
```
|
||||
Please list all jobs in the development namespace.
|
||||
```
|
||||
|
||||
### Check Job Status
|
||||
|
||||
```
|
||||
What is the status of the job "example-job"?
|
||||
```
|
||||
|
||||
### Start a New Job
|
||||
|
||||
```
|
||||
Please create a new job with the following specifications:
|
||||
- Job ID: test-nginx
|
||||
- Docker image: nginx:latest
|
||||
- Memory: 256MB
|
||||
- CPU: 200MHz
|
||||
- Port mapping: HTTP port 80
|
||||
```
|
||||
|
||||
### Stop a Job
|
||||
|
||||
```
|
||||
Please stop the job "test-nginx" and purge it from Nomad.
|
||||
```
|
||||
|
||||
### Get Job Logs
|
||||
|
||||
```
|
||||
Show me the logs for the job "example-job".
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If Claude is unable to connect to the Nomad MCP service, check the following:
|
||||
|
||||
1. Ensure the Nomad MCP service is running and accessible from Claude's network
|
||||
2. Verify the base URL in the tool configuration is correct
|
||||
3. Check that the Nomad MCP service has proper connectivity to the Nomad server
|
||||
4. Review the logs of the Nomad MCP service for any errors
|
||||
|
||||
## Security Considerations
|
||||
|
||||
The Claude API integration does not include authentication by default. If you need to secure the API:
|
||||
|
||||
1. Add an API key requirement to the FastAPI application
|
||||
2. Include the API key in the Claude tool configuration
|
||||
# Claude Integration with Nomad MCP
|
||||
|
||||
This document explains how to configure Claude to connect to the Nomad MCP service and manage jobs.
|
||||
|
||||
## Overview
|
||||
|
||||
The Nomad MCP service provides a simplified REST API specifically designed for Claude to interact with Nomad jobs. This API allows Claude to:
|
||||
|
||||
1. List all jobs in a namespace
|
||||
2. Get the status of a specific job
|
||||
3. Start, stop, and restart jobs
|
||||
4. Create new jobs with a simplified specification
|
||||
5. Retrieve logs from jobs
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The Claude-specific API is available at the `/api/claude` prefix. The following endpoints are available:
|
||||
|
||||
### List Jobs
|
||||
|
||||
```
|
||||
GET /api/claude/list-jobs?namespace=development
|
||||
```
|
||||
|
||||
Returns a list of all jobs in the specified namespace with their IDs, names, statuses, and types.
|
||||
|
||||
### Manage Jobs
|
||||
|
||||
```
|
||||
POST /api/claude/jobs
|
||||
```
|
||||
|
||||
Manages existing jobs with operations like status check, stop, and restart.
|
||||
|
||||
Request body:
|
||||
```json
|
||||
{
|
||||
"job_id": "example-job",
|
||||
"action": "status|stop|restart",
|
||||
"namespace": "development",
|
||||
"purge": false
|
||||
}
|
||||
```
|
||||
|
||||
### Create Jobs
|
||||
|
||||
```
|
||||
POST /api/claude/create-job
|
||||
```
|
||||
|
||||
Creates a new job with a simplified specification.
|
||||
|
||||
Request body:
|
||||
```json
|
||||
{
|
||||
"job_id": "example-job",
|
||||
"name": "Example Job",
|
||||
"type": "service",
|
||||
"datacenters": ["jm"],
|
||||
"namespace": "development",
|
||||
"docker_image": "nginx:latest",
|
||||
"count": 1,
|
||||
"cpu": 100,
|
||||
"memory": 128,
|
||||
"ports": [
|
||||
{
|
||||
"Label": "http",
|
||||
"Value": 0,
|
||||
"To": 80
|
||||
}
|
||||
],
|
||||
"env_vars": {
|
||||
"ENV_VAR1": "value1",
|
||||
"ENV_VAR2": "value2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Job Logs
|
||||
|
||||
```
|
||||
GET /api/claude/job-logs/{job_id}?namespace=development
|
||||
```
|
||||
|
||||
Retrieves logs from the latest allocation of the specified job.
|
||||
|
||||
## Claude Integration Options
|
||||
|
||||
There are three main ways to integrate Claude with the Nomad MCP service:
|
||||
|
||||
### Option 1: Using Claude Desktop/Web UI Tool Configuration
|
||||
|
||||
Use this approach for Claude Web or Desktop applications with the tool configuration feature.
|
||||
|
||||
#### 1. Set Up API Access
|
||||
|
||||
Configure the base URL of your Nomad MCP service:
|
||||
```
|
||||
http://your-server-address:8000
|
||||
```
|
||||
|
||||
#### 2. Create a Claude Tool Configuration
|
||||
|
||||
Create a tool configuration file (see sample in `claude_nomad_tool.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"name": "nomad_mcp",
|
||||
"description": "Manage Nomad jobs through the MCP service",
|
||||
"api_endpoints": [
|
||||
{
|
||||
"name": "list_jobs",
|
||||
"description": "List all jobs in a namespace",
|
||||
"method": "GET",
|
||||
"url": "http://your-server-address:8000/api/claude/list-jobs",
|
||||
"params": [
|
||||
{
|
||||
"name": "namespace",
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"default": "development"
|
||||
}
|
||||
]
|
||||
},
|
||||
// Other endpoints...
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Import the Tool Configuration
|
||||
|
||||
1. Open the Claude web or desktop application
|
||||
2. Go to Settings > Tools
|
||||
3. Click "Import Tool Configuration"
|
||||
4. Select the JSON file with the configuration
|
||||
5. Click "Save"
|
||||
|
||||
### Option 2: Using Claude Code CLI with OpenAPI Tool
|
||||
|
||||
For Claude Code CLI integration with the OpenAPI tool approach:
|
||||
|
||||
#### 1. Install Claude Code CLI
|
||||
|
||||
```bash
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
```
|
||||
|
||||
#### 2. Create an OpenAPI Specification File
|
||||
|
||||
Use the updated `claude_nomad_tool.json` file which follows the OpenAPI specification format:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": "v1",
|
||||
"name": "nomad_mcp",
|
||||
"description": "Manage Nomad jobs through the MCP service",
|
||||
"authentication": {
|
||||
"type": "none"
|
||||
},
|
||||
"api": {
|
||||
"type": "openapi",
|
||||
"url": "",
|
||||
"endpoints": [
|
||||
{
|
||||
"path": "/api/claude/list-jobs",
|
||||
"method": "GET",
|
||||
"description": "List all jobs in a namespace",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "namespace",
|
||||
"in": "query",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "development"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// Other endpoints...
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Register the Tool with Claude Code
|
||||
|
||||
```bash
|
||||
claude-code tools register --specification=/path/to/claude_nomad_tool.json --base-url=http://your-server-address:8000
|
||||
```
|
||||
|
||||
### Option 3: Using Claude Code CLI with SSE (Server-Sent Events)
|
||||
|
||||
For a more interactive experience, you can use Claude Code's MCP (Model Context Protocol) with SSE transport:
|
||||
|
||||
#### 1. Install Claude Code CLI
|
||||
|
||||
```bash
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
```
|
||||
|
||||
#### 2. Start Your Nomad MCP Service
|
||||
|
||||
Make sure your Nomad MCP service is running and accessible.
|
||||
|
||||
#### 3. Add the MCP Configuration to Claude Code
|
||||
|
||||
Use the `claude_code_mcp.json` configuration file with the Claude Code CLI:
|
||||
|
||||
```bash
|
||||
claude-code mcp add nomad_mcp /path/to/claude_code_mcp.json
|
||||
```
|
||||
|
||||
This configuration uses the SSE endpoint at `/api/claude/mcp/stream` to create a streaming connection between Claude Code and your service.
|
||||
|
||||
#### 4. Launch Claude Code with the MCP Provider
|
||||
|
||||
```bash
|
||||
claude-code --mcp nomad_mcp
|
||||
```
|
||||
|
||||
The SSE integration provides a more responsive experience with streaming updates and better error handling compared to the regular tool integration.
|
||||
|
||||
## Test the Connection
|
||||
|
||||
You can test the connection by asking Claude to list all jobs:
|
||||
|
||||
```
|
||||
Please list all jobs in the development namespace using the Nomad MCP service.
|
||||
```
|
||||
|
||||
Claude should use the configured tool to make an API request to the Nomad MCP service and return the list of jobs.
|
||||
|
||||
## Example Prompts for Claude
|
||||
|
||||
Here are some example prompts you can use with Claude to interact with the Nomad MCP service:
|
||||
|
||||
### List Jobs
|
||||
|
||||
```
|
||||
Please list all jobs in the development namespace.
|
||||
```
|
||||
|
||||
### Check Job Status
|
||||
|
||||
```
|
||||
What is the status of the job "example-job"?
|
||||
```
|
||||
|
||||
### Start a New Job
|
||||
|
||||
```
|
||||
Please create a new job with the following specifications:
|
||||
- Job ID: test-nginx
|
||||
- Docker image: nginx:latest
|
||||
- Memory: 256MB
|
||||
- CPU: 200MHz
|
||||
- Port mapping: HTTP port 80
|
||||
```
|
||||
|
||||
### Stop a Job
|
||||
|
||||
```
|
||||
Please stop the job "test-nginx" and purge it from Nomad.
|
||||
```
|
||||
|
||||
### Get Job Logs
|
||||
|
||||
```
|
||||
Show me the logs for the job "example-job".
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If Claude is unable to connect to the Nomad MCP service, check the following:
|
||||
|
||||
1. Ensure the Nomad MCP service is running and accessible from Claude's network
|
||||
2. Verify the base URL in the tool configuration is correct
|
||||
3. Check that the Nomad MCP service has proper connectivity to the Nomad server
|
||||
4. Review the logs of the Nomad MCP service for any errors
|
||||
5. Make sure CORS is enabled in your FastAPI application (already configured in this application)
|
||||
|
||||
## Security Considerations
|
||||
|
||||
The Claude API integration does not include authentication by default. If you need to secure the API:
|
||||
|
||||
1. Add an API key requirement to the FastAPI application
|
||||
2. Include the API key in the Claude tool configuration
|
||||
3. Consider using HTTPS for all communications between Claude and the Nomad MCP service
|
@ -1,230 +1,460 @@
|
||||
from fastapi import APIRouter, HTTPException, Body, Query, Depends
|
||||
from typing import Dict, Any, List, Optional
|
||||
import logging
|
||||
import json
|
||||
|
||||
from app.services.nomad_client import NomadService
|
||||
from app.schemas.claude_api import ClaudeJobRequest, ClaudeJobSpecification, ClaudeJobResponse
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@router.post("/jobs", response_model=ClaudeJobResponse)
|
||||
async def manage_job(request: ClaudeJobRequest):
|
||||
"""
|
||||
Endpoint for Claude to manage Nomad jobs with a simplified interface.
|
||||
|
||||
This endpoint handles job operations like start, stop, restart, and status checks.
|
||||
"""
|
||||
try:
|
||||
# Create a Nomad service instance with the specified namespace
|
||||
nomad_service = NomadService()
|
||||
if request.namespace:
|
||||
nomad_service.namespace = request.namespace
|
||||
|
||||
# Handle different actions
|
||||
if request.action.lower() == "status":
|
||||
# Get job status
|
||||
job = nomad_service.get_job(request.job_id)
|
||||
|
||||
# Get allocations for more detailed status
|
||||
allocations = nomad_service.get_allocations(request.job_id)
|
||||
latest_alloc = None
|
||||
if allocations:
|
||||
# Sort allocations by creation time (descending)
|
||||
sorted_allocations = sorted(
|
||||
allocations,
|
||||
key=lambda a: a.get("CreateTime", 0),
|
||||
reverse=True
|
||||
)
|
||||
latest_alloc = sorted_allocations[0]
|
||||
|
||||
return ClaudeJobResponse(
|
||||
success=True,
|
||||
job_id=request.job_id,
|
||||
status=job.get("Status", "unknown"),
|
||||
message=f"Job {request.job_id} is {job.get('Status', 'unknown')}",
|
||||
details={
|
||||
"job": job,
|
||||
"latest_allocation": latest_alloc
|
||||
}
|
||||
)
|
||||
|
||||
elif request.action.lower() == "stop":
|
||||
# Stop the job
|
||||
result = nomad_service.stop_job(request.job_id, purge=request.purge)
|
||||
|
||||
return ClaudeJobResponse(
|
||||
success=True,
|
||||
job_id=request.job_id,
|
||||
status="stopped",
|
||||
message=f"Job {request.job_id} has been stopped" + (" and purged" if request.purge else ""),
|
||||
details=result
|
||||
)
|
||||
|
||||
elif request.action.lower() == "restart":
|
||||
# Get the current job specification
|
||||
job_spec = nomad_service.get_job(request.job_id)
|
||||
|
||||
# Stop the job
|
||||
nomad_service.stop_job(request.job_id)
|
||||
|
||||
# Start the job with the original specification
|
||||
result = nomad_service.start_job(job_spec)
|
||||
|
||||
return ClaudeJobResponse(
|
||||
success=True,
|
||||
job_id=request.job_id,
|
||||
status="restarted",
|
||||
message=f"Job {request.job_id} has been restarted",
|
||||
details=result
|
||||
)
|
||||
|
||||
else:
|
||||
# Unknown action
|
||||
raise HTTPException(status_code=400, detail=f"Unknown action: {request.action}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error managing job {request.job_id}: {str(e)}")
|
||||
return ClaudeJobResponse(
|
||||
success=False,
|
||||
job_id=request.job_id,
|
||||
status="error",
|
||||
message=f"Error: {str(e)}",
|
||||
details=None
|
||||
)
|
||||
|
||||
@router.post("/create-job", response_model=ClaudeJobResponse)
|
||||
async def create_job(job_spec: ClaudeJobSpecification):
|
||||
"""
|
||||
Endpoint for Claude to create a new Nomad job with a simplified interface.
|
||||
|
||||
This endpoint allows creating a job with minimal configuration.
|
||||
"""
|
||||
try:
|
||||
# Create a Nomad service instance with the specified namespace
|
||||
nomad_service = NomadService()
|
||||
if job_spec.namespace:
|
||||
nomad_service.namespace = job_spec.namespace
|
||||
|
||||
# Convert the simplified job spec to Nomad format
|
||||
nomad_job_spec = job_spec.to_nomad_job_spec()
|
||||
|
||||
# Start the job
|
||||
result = nomad_service.start_job(nomad_job_spec)
|
||||
|
||||
return ClaudeJobResponse(
|
||||
success=True,
|
||||
job_id=job_spec.job_id,
|
||||
status="started",
|
||||
message=f"Job {job_spec.job_id} has been created and started",
|
||||
details=result
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating job {job_spec.job_id}: {str(e)}")
|
||||
return ClaudeJobResponse(
|
||||
success=False,
|
||||
job_id=job_spec.job_id,
|
||||
status="error",
|
||||
message=f"Error: {str(e)}",
|
||||
details=None
|
||||
)
|
||||
|
||||
@router.get("/list-jobs", response_model=List[Dict[str, Any]])
|
||||
async def list_jobs(namespace: str = Query("development")):
|
||||
"""
|
||||
List all jobs in the specified namespace.
|
||||
|
||||
Returns a simplified list of jobs with their IDs and statuses.
|
||||
"""
|
||||
try:
|
||||
# Create a Nomad service instance with the specified namespace
|
||||
nomad_service = NomadService()
|
||||
nomad_service.namespace = namespace
|
||||
|
||||
# Get all jobs
|
||||
jobs = nomad_service.list_jobs()
|
||||
|
||||
# Return a simplified list
|
||||
simplified_jobs = []
|
||||
for job in jobs:
|
||||
simplified_jobs.append({
|
||||
"id": job.get("ID"),
|
||||
"name": job.get("Name"),
|
||||
"status": job.get("Status"),
|
||||
"type": job.get("Type"),
|
||||
"namespace": namespace
|
||||
})
|
||||
|
||||
return simplified_jobs
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing jobs: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Error listing jobs: {str(e)}")
|
||||
|
||||
@router.get("/job-logs/{job_id}", response_model=Dict[str, Any])
|
||||
async def get_job_logs(job_id: str, namespace: str = Query("development")):
|
||||
"""
|
||||
Get logs for a job.
|
||||
|
||||
Returns logs from the latest allocation of the job.
|
||||
"""
|
||||
try:
|
||||
# Create a Nomad service instance with the specified namespace
|
||||
nomad_service = NomadService()
|
||||
nomad_service.namespace = namespace
|
||||
|
||||
# Get allocations for the job
|
||||
allocations = nomad_service.get_allocations(job_id)
|
||||
if not allocations:
|
||||
return {
|
||||
"success": False,
|
||||
"job_id": job_id,
|
||||
"message": f"No allocations found for job {job_id}",
|
||||
"logs": None
|
||||
}
|
||||
|
||||
# Sort allocations by creation time (descending)
|
||||
sorted_allocations = sorted(
|
||||
allocations,
|
||||
key=lambda a: a.get("CreateTime", 0),
|
||||
reverse=True
|
||||
)
|
||||
latest_alloc = sorted_allocations[0]
|
||||
alloc_id = latest_alloc.get("ID")
|
||||
|
||||
# Get the task name from the allocation
|
||||
task_name = None
|
||||
if "TaskStates" in latest_alloc:
|
||||
task_states = latest_alloc["TaskStates"]
|
||||
if task_states:
|
||||
task_name = next(iter(task_states.keys()))
|
||||
|
||||
if not task_name:
|
||||
task_name = "app" # Default task name
|
||||
|
||||
# Get logs for the allocation
|
||||
stdout_logs = nomad_service.get_allocation_logs(alloc_id, task_name, "stdout")
|
||||
stderr_logs = nomad_service.get_allocation_logs(alloc_id, task_name, "stderr")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"job_id": job_id,
|
||||
"allocation_id": alloc_id,
|
||||
"task_name": task_name,
|
||||
"message": f"Retrieved logs for job {job_id}",
|
||||
"logs": {
|
||||
"stdout": stdout_logs,
|
||||
"stderr": stderr_logs
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting logs for job {job_id}: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"job_id": job_id,
|
||||
"message": f"Error getting logs: {str(e)}",
|
||||
"logs": None
|
||||
}
|
||||
from fastapi import APIRouter, HTTPException, Body, Query, Depends, Request
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Dict, Any, List, Optional, AsyncGenerator
|
||||
import logging
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
from app.services.nomad_client import NomadService
|
||||
from app.schemas.claude_api import ClaudeJobRequest, ClaudeJobSpecification, ClaudeJobResponse
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@router.post("/jobs", response_model=ClaudeJobResponse)
|
||||
async def manage_job(request: ClaudeJobRequest):
|
||||
"""
|
||||
Endpoint for Claude to manage Nomad jobs with a simplified interface.
|
||||
|
||||
This endpoint handles job operations like start, stop, restart, and status checks.
|
||||
"""
|
||||
try:
|
||||
# Create a Nomad service instance with the specified namespace
|
||||
nomad_service = NomadService()
|
||||
if request.namespace:
|
||||
nomad_service.namespace = request.namespace
|
||||
|
||||
# Handle different actions
|
||||
if request.action.lower() == "status":
|
||||
# Get job status
|
||||
job = nomad_service.get_job(request.job_id)
|
||||
|
||||
# Get allocations for more detailed status
|
||||
allocations = nomad_service.get_allocations(request.job_id)
|
||||
latest_alloc = None
|
||||
if allocations:
|
||||
# Sort allocations by creation time (descending)
|
||||
sorted_allocations = sorted(
|
||||
allocations,
|
||||
key=lambda a: a.get("CreateTime", 0),
|
||||
reverse=True
|
||||
)
|
||||
latest_alloc = sorted_allocations[0]
|
||||
|
||||
return ClaudeJobResponse(
|
||||
success=True,
|
||||
job_id=request.job_id,
|
||||
status=job.get("Status", "unknown"),
|
||||
message=f"Job {request.job_id} is {job.get('Status', 'unknown')}",
|
||||
details={
|
||||
"job": job,
|
||||
"latest_allocation": latest_alloc
|
||||
}
|
||||
)
|
||||
|
||||
elif request.action.lower() == "stop":
|
||||
# Stop the job
|
||||
result = nomad_service.stop_job(request.job_id, purge=request.purge)
|
||||
|
||||
return ClaudeJobResponse(
|
||||
success=True,
|
||||
job_id=request.job_id,
|
||||
status="stopped",
|
||||
message=f"Job {request.job_id} has been stopped" + (" and purged" if request.purge else ""),
|
||||
details=result
|
||||
)
|
||||
|
||||
elif request.action.lower() == "restart":
|
||||
# Get the current job specification
|
||||
job_spec = nomad_service.get_job(request.job_id)
|
||||
|
||||
# Stop the job
|
||||
nomad_service.stop_job(request.job_id)
|
||||
|
||||
# Start the job with the original specification
|
||||
result = nomad_service.start_job(job_spec)
|
||||
|
||||
return ClaudeJobResponse(
|
||||
success=True,
|
||||
job_id=request.job_id,
|
||||
status="restarted",
|
||||
message=f"Job {request.job_id} has been restarted",
|
||||
details=result
|
||||
)
|
||||
|
||||
else:
|
||||
# Unknown action
|
||||
raise HTTPException(status_code=400, detail=f"Unknown action: {request.action}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error managing job {request.job_id}: {str(e)}")
|
||||
return ClaudeJobResponse(
|
||||
success=False,
|
||||
job_id=request.job_id,
|
||||
status="error",
|
||||
message=f"Error: {str(e)}",
|
||||
details=None
|
||||
)
|
||||
|
||||
@router.post("/create-job", response_model=ClaudeJobResponse)
|
||||
async def create_job(job_spec: ClaudeJobSpecification):
|
||||
"""
|
||||
Endpoint for Claude to create a new Nomad job with a simplified interface.
|
||||
|
||||
This endpoint allows creating a job with minimal configuration.
|
||||
"""
|
||||
try:
|
||||
# Create a Nomad service instance with the specified namespace
|
||||
nomad_service = NomadService()
|
||||
if job_spec.namespace:
|
||||
nomad_service.namespace = job_spec.namespace
|
||||
|
||||
# Convert the simplified job spec to Nomad format
|
||||
nomad_job_spec = job_spec.to_nomad_job_spec()
|
||||
|
||||
# Start the job
|
||||
result = nomad_service.start_job(nomad_job_spec)
|
||||
|
||||
return ClaudeJobResponse(
|
||||
success=True,
|
||||
job_id=job_spec.job_id,
|
||||
status="started",
|
||||
message=f"Job {job_spec.job_id} has been created and started",
|
||||
details=result
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating job {job_spec.job_id}: {str(e)}")
|
||||
return ClaudeJobResponse(
|
||||
success=False,
|
||||
job_id=job_spec.job_id,
|
||||
status="error",
|
||||
message=f"Error: {str(e)}",
|
||||
details=None
|
||||
)
|
||||
|
||||
@router.get("/list-jobs", response_model=List[Dict[str, Any]])
|
||||
async def list_jobs(namespace: str = Query("development")):
|
||||
"""
|
||||
List all jobs in the specified namespace.
|
||||
|
||||
Returns a simplified list of jobs with their IDs and statuses.
|
||||
"""
|
||||
try:
|
||||
# Create a Nomad service instance with the specified namespace
|
||||
nomad_service = NomadService()
|
||||
nomad_service.namespace = namespace
|
||||
|
||||
# Get all jobs
|
||||
jobs = nomad_service.list_jobs()
|
||||
|
||||
# Return a simplified list
|
||||
simplified_jobs = []
|
||||
for job in jobs:
|
||||
simplified_jobs.append({
|
||||
"id": job.get("ID"),
|
||||
"name": job.get("Name"),
|
||||
"status": job.get("Status"),
|
||||
"type": job.get("Type"),
|
||||
"namespace": namespace
|
||||
})
|
||||
|
||||
return simplified_jobs
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing jobs: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Error listing jobs: {str(e)}")
|
||||
|
||||
@router.get("/job-logs/{job_id}", response_model=Dict[str, Any])
|
||||
async def get_job_logs(job_id: str, namespace: str = Query("development")):
|
||||
"""
|
||||
Get logs for a job.
|
||||
|
||||
Returns logs from the latest allocation of the job.
|
||||
"""
|
||||
try:
|
||||
# Create a Nomad service instance with the specified namespace
|
||||
nomad_service = NomadService()
|
||||
nomad_service.namespace = namespace
|
||||
|
||||
# Get allocations for the job
|
||||
allocations = nomad_service.get_allocations(job_id)
|
||||
if not allocations:
|
||||
return {
|
||||
"success": False,
|
||||
"job_id": job_id,
|
||||
"message": f"No allocations found for job {job_id}",
|
||||
"logs": None
|
||||
}
|
||||
|
||||
# Sort allocations by creation time (descending)
|
||||
sorted_allocations = sorted(
|
||||
allocations,
|
||||
key=lambda a: a.get("CreateTime", 0),
|
||||
reverse=True
|
||||
)
|
||||
latest_alloc = sorted_allocations[0]
|
||||
alloc_id = latest_alloc.get("ID")
|
||||
|
||||
# Get the task name from the allocation
|
||||
task_name = None
|
||||
if "TaskStates" in latest_alloc:
|
||||
task_states = latest_alloc["TaskStates"]
|
||||
if task_states:
|
||||
task_name = next(iter(task_states.keys()))
|
||||
|
||||
if not task_name:
|
||||
task_name = "app" # Default task name
|
||||
|
||||
# Get logs for the allocation
|
||||
stdout_logs = nomad_service.get_allocation_logs(alloc_id, task_name, "stdout")
|
||||
stderr_logs = nomad_service.get_allocation_logs(alloc_id, task_name, "stderr")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"job_id": job_id,
|
||||
"allocation_id": alloc_id,
|
||||
"task_name": task_name,
|
||||
"message": f"Retrieved logs for job {job_id}",
|
||||
"logs": {
|
||||
"stdout": stdout_logs,
|
||||
"stderr": stderr_logs
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting logs for job {job_id}: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"job_id": job_id,
|
||||
"message": f"Error getting logs: {str(e)}",
|
||||
"logs": None
|
||||
}
|
||||
|
||||
@router.post("/mcp/stream")
|
||||
async def mcp_stream(request: Request):
|
||||
"""
|
||||
MCP streaming endpoint for Claude Code using Server-Sent Events (SSE).
|
||||
|
||||
This endpoint allows Claude Code to connect using the SSE transport mode.
|
||||
It receives requests from Claude Code and streams responses back.
|
||||
"""
|
||||
async def event_generator() -> AsyncGenerator[str, None]:
|
||||
# Read request data
|
||||
request_data = await request.json()
|
||||
logger.info(f"Received MCP request: {request_data}")
|
||||
|
||||
# Extract the request type and content
|
||||
request_type = request_data.get("type", "unknown")
|
||||
request_content = request_data.get("content", {})
|
||||
request_id = request_data.get("id", "unknown")
|
||||
|
||||
# Send initial acknowledgment
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'ack', 'content': 'Processing request'})}\n\n"
|
||||
|
||||
try:
|
||||
# Process different request types
|
||||
if request_type == "nomad_list_jobs":
|
||||
# Handle list jobs request
|
||||
namespace = request_content.get("namespace", "development")
|
||||
|
||||
# Create Nomad service and get jobs
|
||||
nomad_service = NomadService()
|
||||
nomad_service.namespace = namespace
|
||||
jobs = nomad_service.list_jobs()
|
||||
|
||||
# Format job information
|
||||
simplified_jobs = []
|
||||
for job in jobs:
|
||||
simplified_jobs.append({
|
||||
"id": job.get("ID"),
|
||||
"name": job.get("Name"),
|
||||
"status": job.get("Status"),
|
||||
"type": job.get("Type"),
|
||||
"namespace": namespace
|
||||
})
|
||||
|
||||
# Send result
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'result', 'content': simplified_jobs})}\n\n"
|
||||
|
||||
elif request_type == "nomad_job_status":
|
||||
# Handle job status request
|
||||
job_id = request_content.get("job_id")
|
||||
namespace = request_content.get("namespace", "development")
|
||||
|
||||
if not job_id:
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'error', 'content': 'Job ID is required'})}\n\n"
|
||||
else:
|
||||
# Create Nomad service and get job status
|
||||
nomad_service = NomadService()
|
||||
nomad_service.namespace = namespace
|
||||
job = nomad_service.get_job(job_id)
|
||||
|
||||
# Get latest allocation
|
||||
allocations = nomad_service.get_allocations(job_id)
|
||||
latest_alloc = None
|
||||
if allocations:
|
||||
sorted_allocations = sorted(
|
||||
allocations,
|
||||
key=lambda a: a.get("CreateTime", 0),
|
||||
reverse=True
|
||||
)
|
||||
latest_alloc = sorted_allocations[0]
|
||||
|
||||
# Send result
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'result', 'content': {
|
||||
'job_id': job_id,
|
||||
'status': job.get('Status', 'unknown'),
|
||||
'message': f'Job {job_id} is {job.get('Status', 'unknown')}',
|
||||
'details': {
|
||||
'job': job,
|
||||
'latest_allocation': latest_alloc
|
||||
}
|
||||
}})}\n\n"
|
||||
|
||||
elif request_type == "nomad_job_action":
|
||||
# Handle job action request (stop, restart)
|
||||
job_id = request_content.get("job_id")
|
||||
action = request_content.get("action")
|
||||
namespace = request_content.get("namespace", "development")
|
||||
purge = request_content.get("purge", False)
|
||||
|
||||
if not job_id or not action:
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'error', 'content': 'Job ID and action are required'})}\n\n"
|
||||
else:
|
||||
# Create Nomad service
|
||||
nomad_service = NomadService()
|
||||
nomad_service.namespace = namespace
|
||||
|
||||
if action.lower() == "stop":
|
||||
# Stop the job
|
||||
result = nomad_service.stop_job(job_id, purge=purge)
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'result', 'content': {
|
||||
'job_id': job_id,
|
||||
'status': 'stopped',
|
||||
'message': f'Job {job_id} has been stopped' + (' and purged' if purge else ''),
|
||||
'details': result
|
||||
}})}\n\n"
|
||||
|
||||
elif action.lower() == "restart":
|
||||
# Restart the job
|
||||
job_spec = nomad_service.get_job(job_id)
|
||||
nomad_service.stop_job(job_id)
|
||||
result = nomad_service.start_job(job_spec)
|
||||
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'result', 'content': {
|
||||
'job_id': job_id,
|
||||
'status': 'restarted',
|
||||
'message': f'Job {job_id} has been restarted',
|
||||
'details': result
|
||||
}})}\n\n"
|
||||
else:
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'error', 'content': f'Unknown action: {action}'})}\n\n"
|
||||
|
||||
elif request_type == "nomad_create_job":
|
||||
# Handle create job request
|
||||
try:
|
||||
# Convert request content to job spec
|
||||
job_spec = ClaudeJobSpecification(**request_content)
|
||||
|
||||
# Create Nomad service
|
||||
nomad_service = NomadService()
|
||||
if job_spec.namespace:
|
||||
nomad_service.namespace = job_spec.namespace
|
||||
|
||||
# Convert to Nomad format and start job
|
||||
nomad_job_spec = job_spec.to_nomad_job_spec()
|
||||
result = nomad_service.start_job(nomad_job_spec)
|
||||
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'result', 'content': {
|
||||
'job_id': job_spec.job_id,
|
||||
'status': 'started',
|
||||
'message': f'Job {job_spec.job_id} has been created and started',
|
||||
'details': result
|
||||
}})}\n\n"
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating job: {str(e)}")
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'error', 'content': f'Error creating job: {str(e)}'})}\n\n"
|
||||
|
||||
elif request_type == "nomad_job_logs":
|
||||
# Handle job logs request
|
||||
job_id = request_content.get("job_id")
|
||||
namespace = request_content.get("namespace", "development")
|
||||
|
||||
if not job_id:
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'error', 'content': 'Job ID is required'})}\n\n"
|
||||
else:
|
||||
try:
|
||||
# Create Nomad service
|
||||
nomad_service = NomadService()
|
||||
nomad_service.namespace = namespace
|
||||
|
||||
# Get job allocations
|
||||
allocations = nomad_service.get_allocations(job_id)
|
||||
if not allocations:
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'result', 'content': {
|
||||
'success': False,
|
||||
'job_id': job_id,
|
||||
'message': f'No allocations found for job {job_id}',
|
||||
'logs': None
|
||||
}})}\n\n"
|
||||
else:
|
||||
# Get latest allocation and logs
|
||||
sorted_allocations = sorted(
|
||||
allocations,
|
||||
key=lambda a: a.get("CreateTime", 0),
|
||||
reverse=True
|
||||
)
|
||||
latest_alloc = sorted_allocations[0]
|
||||
alloc_id = latest_alloc.get("ID")
|
||||
|
||||
# Get task name
|
||||
task_name = None
|
||||
if "TaskStates" in latest_alloc:
|
||||
task_states = latest_alloc["TaskStates"]
|
||||
if task_states:
|
||||
task_name = next(iter(task_states.keys()))
|
||||
|
||||
if not task_name:
|
||||
task_name = "app" # Default task name
|
||||
|
||||
# Get logs
|
||||
stdout_logs = nomad_service.get_allocation_logs(alloc_id, task_name, "stdout")
|
||||
stderr_logs = nomad_service.get_allocation_logs(alloc_id, task_name, "stderr")
|
||||
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'result', 'content': {
|
||||
'success': True,
|
||||
'job_id': job_id,
|
||||
'allocation_id': alloc_id,
|
||||
'task_name': task_name,
|
||||
'message': f'Retrieved logs for job {job_id}',
|
||||
'logs': {
|
||||
'stdout': stdout_logs,
|
||||
'stderr': stderr_logs
|
||||
}
|
||||
}})}\n\n"
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting logs for job {job_id}: {str(e)}")
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'error', 'content': f'Error getting logs: {str(e)}'})}\n\n"
|
||||
|
||||
else:
|
||||
# Unknown request type
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'error', 'content': f'Unknown request type: {request_type}'})}\n\n"
|
||||
|
||||
# Send completion message
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'done'})}\n\n"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing MCP request: {str(e)}")
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'error', 'content': f'Internal server error: {str(e)}'})}\n\n"
|
||||
yield f"data: {json.dumps({'id': request_id, 'type': 'done'})}\n\n"
|
||||
|
||||
return StreamingResponse(
|
||||
event_generator(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no", # Needed for Nginx
|
||||
}
|
||||
)
|
249
claude_code_mcp.json
Normal file
249
claude_code_mcp.json
Normal file
@ -0,0 +1,249 @@
|
||||
{
|
||||
"name": "nomad_mcp_sse",
|
||||
"version": "1.0.0",
|
||||
"description": "Nomad MCP service for Claude Code using SSE",
|
||||
"transport": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8000/api/claude/mcp/stream"
|
||||
},
|
||||
"authentication": {
|
||||
"type": "none"
|
||||
},
|
||||
"capabilities": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "list_nomad_jobs",
|
||||
"description": "List all Nomad jobs in a namespace",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"default": "development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"implementation": {
|
||||
"type": "nomad_list_jobs",
|
||||
"content": {
|
||||
"namespace": "${parameters.namespace}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_job_status",
|
||||
"description": "Get the status of a specific Nomad job",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"description": "ID of the job to check"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"default": "development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["job_id"]
|
||||
},
|
||||
"implementation": {
|
||||
"type": "nomad_job_status",
|
||||
"content": {
|
||||
"job_id": "${parameters.job_id}",
|
||||
"namespace": "${parameters.namespace}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "stop_job",
|
||||
"description": "Stop a running Nomad job",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"description": "ID of the job to stop"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"default": "development"
|
||||
},
|
||||
"purge": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to purge the job",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["job_id"]
|
||||
},
|
||||
"implementation": {
|
||||
"type": "nomad_job_action",
|
||||
"content": {
|
||||
"job_id": "${parameters.job_id}",
|
||||
"action": "stop",
|
||||
"namespace": "${parameters.namespace}",
|
||||
"purge": "${parameters.purge}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "restart_job",
|
||||
"description": "Restart a Nomad job",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"description": "ID of the job to restart"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"default": "development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["job_id"]
|
||||
},
|
||||
"implementation": {
|
||||
"type": "nomad_job_action",
|
||||
"content": {
|
||||
"job_id": "${parameters.job_id}",
|
||||
"action": "restart",
|
||||
"namespace": "${parameters.namespace}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "create_job",
|
||||
"description": "Create a new Nomad job",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"description": "Unique ID for the job"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Display name for the job"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Job type (service, batch, etc.)",
|
||||
"default": "service"
|
||||
},
|
||||
"datacenters": {
|
||||
"type": "array",
|
||||
"description": "List of datacenters to run the job in",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": ["jm"]
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"default": "development"
|
||||
},
|
||||
"docker_image": {
|
||||
"type": "string",
|
||||
"description": "Docker image to run"
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "Number of instances to run",
|
||||
"default": 1
|
||||
},
|
||||
"cpu": {
|
||||
"type": "integer",
|
||||
"description": "CPU allocation in MHz",
|
||||
"default": 100
|
||||
},
|
||||
"memory": {
|
||||
"type": "integer",
|
||||
"description": "Memory allocation in MB",
|
||||
"default": 128
|
||||
},
|
||||
"ports": {
|
||||
"type": "array",
|
||||
"description": "Port mappings",
|
||||
"items": {
|
||||
"type": "object"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"env_vars": {
|
||||
"type": "object",
|
||||
"description": "Environment variables for the container",
|
||||
"default": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["job_id", "name", "docker_image"]
|
||||
},
|
||||
"implementation": {
|
||||
"type": "nomad_create_job",
|
||||
"content": {
|
||||
"job_id": "${parameters.job_id}",
|
||||
"name": "${parameters.name}",
|
||||
"type": "${parameters.type}",
|
||||
"datacenters": "${parameters.datacenters}",
|
||||
"namespace": "${parameters.namespace}",
|
||||
"docker_image": "${parameters.docker_image}",
|
||||
"count": "${parameters.count}",
|
||||
"cpu": "${parameters.cpu}",
|
||||
"memory": "${parameters.memory}",
|
||||
"ports": "${parameters.ports}",
|
||||
"env_vars": "${parameters.env_vars}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_job_logs",
|
||||
"description": "Get logs for a Nomad job",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"description": "ID of the job to get logs for"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"default": "development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["job_id"]
|
||||
},
|
||||
"implementation": {
|
||||
"type": "nomad_job_logs",
|
||||
"content": {
|
||||
"job_id": "${parameters.job_id}",
|
||||
"namespace": "${parameters.namespace}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -1,71 +1,159 @@
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"name": "nomad_mcp",
|
||||
"description": "Manage Nomad jobs through the MCP service",
|
||||
"api_endpoints": [
|
||||
{
|
||||
"name": "list_jobs",
|
||||
"description": "List all jobs in a namespace",
|
||||
"method": "GET",
|
||||
"url": "http://127.0.0.1:8000/api/claude/list-jobs",
|
||||
"params": [
|
||||
{
|
||||
"name": "namespace",
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"default": "development"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "manage_job",
|
||||
"description": "Manage a job (status, stop, restart)",
|
||||
"method": "POST",
|
||||
"url": "http://127.0.0.1:8000/api/claude/jobs",
|
||||
"body": {
|
||||
"job_id": "string",
|
||||
"action": "string",
|
||||
"namespace": "string",
|
||||
"purge": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_job",
|
||||
"description": "Create a new job",
|
||||
"method": "POST",
|
||||
"url": "http://127.0.0.1:8000/api/claude/create-job",
|
||||
"body": {
|
||||
"job_id": "string",
|
||||
"name": "string",
|
||||
"type": "string",
|
||||
"datacenters": "array",
|
||||
"namespace": "string",
|
||||
"docker_image": "string",
|
||||
"count": "integer",
|
||||
"cpu": "integer",
|
||||
"memory": "integer",
|
||||
"ports": "array",
|
||||
"env_vars": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_job_logs",
|
||||
"description": "Get logs for a job",
|
||||
"method": "GET",
|
||||
"url": "http://127.0.0.1:8000/api/claude/job-logs/{job_id}",
|
||||
"params": [
|
||||
{
|
||||
"name": "namespace",
|
||||
"type": "string",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"default": "development"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
"schema_version": "v1",
|
||||
"name": "nomad_mcp",
|
||||
"description": "Manage Nomad jobs through the MCP service",
|
||||
"authentication": {
|
||||
"type": "none"
|
||||
},
|
||||
"api": {
|
||||
"type": "openapi",
|
||||
"url": "",
|
||||
"endpoints": [
|
||||
{
|
||||
"path": "/api/claude/list-jobs",
|
||||
"method": "GET",
|
||||
"description": "List all jobs in a namespace",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "namespace",
|
||||
"in": "query",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "development"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/api/claude/jobs",
|
||||
"method": "POST",
|
||||
"description": "Manage a job (status, stop, restart)",
|
||||
"request_body": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["job_id", "action"],
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"description": "ID of the job to manage"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "Action to perform: status, stop, or restart"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "Nomad namespace"
|
||||
},
|
||||
"purge": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to purge the job when stopping"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/api/claude/create-job",
|
||||
"method": "POST",
|
||||
"description": "Create a new job",
|
||||
"request_body": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["job_id", "name", "type", "docker_image"],
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"description": "Unique ID for the job"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Display name for the job"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Job type (service, batch, etc.)"
|
||||
},
|
||||
"datacenters": {
|
||||
"type": "array",
|
||||
"description": "List of datacenters to run the job in",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "Nomad namespace"
|
||||
},
|
||||
"docker_image": {
|
||||
"type": "string",
|
||||
"description": "Docker image to run"
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "Number of instances to run"
|
||||
},
|
||||
"cpu": {
|
||||
"type": "integer",
|
||||
"description": "CPU allocation in MHz"
|
||||
},
|
||||
"memory": {
|
||||
"type": "integer",
|
||||
"description": "Memory allocation in MB"
|
||||
},
|
||||
"ports": {
|
||||
"type": "array",
|
||||
"description": "Port mappings",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"env_vars": {
|
||||
"type": "object",
|
||||
"description": "Environment variables for the container"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/api/claude/job-logs/{job_id}",
|
||||
"method": "GET",
|
||||
"description": "Get logs for a job",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "job_id",
|
||||
"in": "path",
|
||||
"description": "ID of the job to get logs for",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "namespace",
|
||||
"in": "query",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "development"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user