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.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
### 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
|
||||
{
|
||||
@ -122,67 +125,109 @@ In the Claude desktop application, you can create a custom tool configuration th
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
// Other endpoints...
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
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"
|
||||
|
||||
### 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:
|
||||
|
||||
@ -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
|
||||
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
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
from fastapi import APIRouter, HTTPException, Body, Query, Depends
|
||||
from typing import Dict, Any, List, Optional
|
||||
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
|
||||
@ -228,3 +230,231 @@ async def get_job_logs(job_id: str, namespace: str = Query("development")):
|
||||
"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": [
|
||||
{
|
||||
"schema_version": "v1",
|
||||
"name": "nomad_mcp",
|
||||
"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",
|
||||
"method": "GET",
|
||||
"url": "http://127.0.0.1:8000/api/claude/list-jobs",
|
||||
"params": [
|
||||
"parameters": [
|
||||
{
|
||||
"name": "namespace",
|
||||
"type": "string",
|
||||
"in": "query",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "development"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "manage_job",
|
||||
"path": "/api/claude/jobs",
|
||||
"method": "POST",
|
||||
"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",
|
||||
"request_body": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["job_id", "action"],
|
||||
"properties": {
|
||||
"job_id": {
|
||||
"type": "string",
|
||||
"datacenters": "array",
|
||||
"namespace": "string",
|
||||
"docker_image": "string",
|
||||
"count": "integer",
|
||||
"cpu": "integer",
|
||||
"memory": "integer",
|
||||
"ports": "array",
|
||||
"env_vars": "object"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_job_logs",
|
||||
"description": "Get logs for a job",
|
||||
"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",
|
||||
"url": "http://127.0.0.1:8000/api/claude/job-logs/{job_id}",
|
||||
"params": [
|
||||
"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",
|
||||
"type": "string",
|
||||
"in": "query",
|
||||
"description": "Nomad namespace",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "development"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user