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.
|
@ -84,21 +84,24 @@ GET /api/claude/job-logs/{job_id}?namespace=development
|
|||||||
|
|
||||||
Retrieves logs from the latest allocation of the specified job.
|
Retrieves logs from the latest allocation of the specified job.
|
||||||
|
|
||||||
## Configuring Claude Desktop Application
|
## Claude Integration Options
|
||||||
|
|
||||||
To configure Claude to connect to the Nomad MCP service, follow these steps:
|
There are three main ways to integrate Claude with the Nomad MCP service:
|
||||||
|
|
||||||
### 1. Set Up API Access
|
### Option 1: Using Claude Desktop/Web UI Tool Configuration
|
||||||
|
|
||||||
Claude needs to be configured with the base URL of your Nomad MCP service. This is typically:
|
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
|
http://your-server-address:8000
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Create a Claude Tool Configuration
|
#### 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:
|
Create a tool configuration file (see sample in `claude_nomad_tool.json`):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -122,67 +125,109 @@ In the Claude desktop application, you can create a custom tool configuration th
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
// Other endpoints...
|
||||||
"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
|
#### 3. Import the Tool Configuration
|
||||||
|
|
||||||
1. Open the Claude desktop application
|
1. Open the Claude web or desktop application
|
||||||
2. Go to Settings > Tools
|
2. Go to Settings > Tools
|
||||||
3. Click "Import Tool Configuration"
|
3. Click "Import Tool Configuration"
|
||||||
4. Select the JSON file with the above configuration
|
4. Select the JSON file with the configuration
|
||||||
5. Click "Save"
|
5. Click "Save"
|
||||||
|
|
||||||
### 4. Test the Connection
|
### 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:
|
You can test the connection by asking Claude to list all jobs:
|
||||||
|
|
||||||
@ -239,6 +284,7 @@ If Claude is unable to connect to the Nomad MCP service, check the following:
|
|||||||
2. Verify the base URL in the tool configuration is correct
|
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
|
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
|
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
|
## Security Considerations
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Body, Query, Depends
|
from fastapi import APIRouter, HTTPException, Body, Query, Depends, Request
|
||||||
from typing import Dict, Any, List, Optional
|
from fastapi.responses import StreamingResponse
|
||||||
|
from typing import Dict, Any, List, Optional, AsyncGenerator
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from app.services.nomad_client import NomadService
|
from app.services.nomad_client import NomadService
|
||||||
from app.schemas.claude_api import ClaudeJobRequest, ClaudeJobSpecification, ClaudeJobResponse
|
from app.schemas.claude_api import ClaudeJobRequest, ClaudeJobSpecification, ClaudeJobResponse
|
||||||
@ -228,3 +230,231 @@ async def get_job_logs(job_id: str, namespace: str = Query("development")):
|
|||||||
"message": f"Error getting logs: {str(e)}",
|
"message": f"Error getting logs: {str(e)}",
|
||||||
"logs": None
|
"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": [
|
"schema_version": "v1",
|
||||||
{
|
|
||||||
"name": "nomad_mcp",
|
"name": "nomad_mcp",
|
||||||
"description": "Manage Nomad jobs through the MCP service",
|
"description": "Manage Nomad jobs through the MCP service",
|
||||||
"api_endpoints": [
|
"authentication": {
|
||||||
|
"type": "none"
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"type": "openapi",
|
||||||
|
"url": "",
|
||||||
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"name": "list_jobs",
|
"path": "/api/claude/list-jobs",
|
||||||
|
"method": "GET",
|
||||||
"description": "List all jobs in a namespace",
|
"description": "List all jobs in a namespace",
|
||||||
"method": "GET",
|
"parameters": [
|
||||||
"url": "http://127.0.0.1:8000/api/claude/list-jobs",
|
|
||||||
"params": [
|
|
||||||
{
|
{
|
||||||
"name": "namespace",
|
"name": "namespace",
|
||||||
"type": "string",
|
"in": "query",
|
||||||
"description": "Nomad namespace",
|
"description": "Nomad namespace",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
"default": "development"
|
"default": "development"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "manage_job",
|
"path": "/api/claude/jobs",
|
||||||
|
"method": "POST",
|
||||||
"description": "Manage a job (status, stop, restart)",
|
"description": "Manage a job (status, stop, restart)",
|
||||||
"method": "POST",
|
"request_body": {
|
||||||
"url": "http://127.0.0.1:8000/api/claude/jobs",
|
"required": true,
|
||||||
"body": {
|
"content": {
|
||||||
"job_id": "string",
|
"application/json": {
|
||||||
"action": "string",
|
"schema": {
|
||||||
"namespace": "string",
|
"type": "object",
|
||||||
"purge": "boolean"
|
"required": ["job_id", "action"],
|
||||||
}
|
"properties": {
|
||||||
},
|
"job_id": {
|
||||||
{
|
|
||||||
"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",
|
"type": "string",
|
||||||
"datacenters": "array",
|
"description": "ID of the job to manage"
|
||||||
"namespace": "string",
|
},
|
||||||
"docker_image": "string",
|
"action": {
|
||||||
"count": "integer",
|
"type": "string",
|
||||||
"cpu": "integer",
|
"description": "Action to perform: status, stop, or restart"
|
||||||
"memory": "integer",
|
},
|
||||||
"ports": "array",
|
"namespace": {
|
||||||
"env_vars": "object"
|
"type": "string",
|
||||||
|
"description": "Nomad namespace"
|
||||||
|
},
|
||||||
|
"purge": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to purge the job when stopping"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "get_job_logs",
|
"path": "/api/claude/create-job",
|
||||||
"description": "Get logs for a 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",
|
"method": "GET",
|
||||||
"url": "http://127.0.0.1:8000/api/claude/job-logs/{job_id}",
|
"description": "Get logs for a job",
|
||||||
"params": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "job_id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "ID of the job to get logs for",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "namespace",
|
"name": "namespace",
|
||||||
"type": "string",
|
"in": "query",
|
||||||
"description": "Nomad namespace",
|
"description": "Nomad namespace",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
"default": "development"
|
"default": "development"
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user