Skip to content

Instantly share code, notes, and snippets.

@bioshazard
Created October 24, 2025 17:33
Show Gist options
  • Save bioshazard/8929a65f2f9670a3e3129b60eaa2ae9c to your computer and use it in GitHub Desktop.
Save bioshazard/8929a65f2f9670a3e3129b60eaa2ae9c to your computer and use it in GitHub Desktop.
MCPO multi-service CLI
#!/bin/bash
# MCPO CLI - Efficient MCP OpenAPI Proxy Client
# Usage: ./mcpo.sh <command> [args...]
set -e
# Configuration
MCPO_HOST="${MCPO_HOST:-http://localhost:8000}"
MCPO_TOKEN="${MCPO_TOKEN:-supersecret}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m' # No Color
# Helper functions
log_info() { echo -e "${BLUE}ℹ${NC} $1" >&2; }
log_success() { echo -e "${GREEN}✓${NC} $1" >&2; }
log_warn() { echo -e "${YELLOW}⚠${NC} $1" >&2; }
log_error() { echo -e "${RED}✗${NC} $1" >&2; }
usage() {
cat << EOF
${BOLD}MCPO CLI - MCP OpenAPI Proxy Client${NC}
${BOLD}USAGE:${NC}
mcpo.sh discover # List available services
mcpo.sh describe <service> # Get service details
mcpo.sh operations <service> # List service operations
mcpo.sh schema <service> <operation> # Get operation schema
mcpo.sh call <service> <operation> [json] # Call service operation
${BOLD}EXAMPLES:${NC}
mcpo.sh discover
mcpo.sh describe context7
mcpo.sh operations memory
mcpo.sh call context7 resolve-library-id '{"libraryName": "react"}'
mcpo.sh call memory search_nodes '{"query": "authentication"}'
${BOLD}ENVIRONMENT:${NC}
MCPO_HOST Target MCPO server (default: http://localhost:8000)
MCPO_TOKEN Bearer token for authentication (default: supersecret)
EOF
}
# Get authentication headers
get_auth_headers() {
if [[ -n "$MCPO_TOKEN" ]]; then
echo "Authorization:Bearer $MCPO_TOKEN"
fi
}
# Discover available services
discover_services() {
log_info "Discovering services at $MCPO_HOST..."
local response
response=$(http GET "$MCPO_HOST/openapi.json" 2>/dev/null || {
log_error "Failed to connect to MCPO server at $MCPO_HOST"
exit 1
})
echo "$response" | jq -r '
if .info.description then
.info.description |
split("\n") |
map(select(test("^\\s*-\\s*\\*\\*"))) |
map(gsub("^\\s*-\\s*\\*\\*([^*]+)\\*\\*.*\\[([^]]+)\\].*"; "\\1: \\2"))[]
else
empty
end
' 2>/dev/null || {
log_warn "Could not parse service list from response"
echo "$response"
}
}
# Get service description
describe_service() {
local service="$1"
[[ -z "$service" ]] && { log_error "Service name required"; usage; exit 1; }
log_info "Getting description for service: $service"
local auth_header
auth_header=$(get_auth_headers)
local response
if [[ -n "$auth_header" ]]; then
response=$(http GET "$MCPO_HOST/$service/openapi.json" "$auth_header" 2>/dev/null)
else
response=$(http GET "$MCPO_HOST/$service/openapi.json" 2>/dev/null)
fi
if [[ $? -ne 0 ]]; then
log_error "Failed to get schema for service: $service"
exit 1
fi
echo "$response" | jq -r '
"Service: " + .info.title,
"Version: " + .info.version,
"Description: " + (.info.description // "No description available"),
"",
"Base URL: " + (.servers[0].url // "/")
'
}
# List service operations
list_operations() {
local service="$1"
[[ -z "$service" ]] && { log_error "Service name required"; usage; exit 1; }
log_info "Listing operations for service: $service"
local auth_header
auth_header=$(get_auth_headers)
local response
if [[ -n "$auth_header" ]]; then
response=$(http GET "$MCPO_HOST/$service/openapi.json" "$auth_header" 2>/dev/null)
else
response=$(http GET "$MCPO_HOST/$service/openapi.json" 2>/dev/null)
fi
if [[ $? -ne 0 ]]; then
log_error "Failed to get schema for service: $service"
exit 1
fi
echo "$response" | jq -r '
.paths | to_entries[] |
.key as $path |
.value | to_entries[] |
.key as $method |
.value |
"Operation: " + ($path | ltrimstr("/")) + " (" + ($method | ascii_upcase) + ")",
"Summary: " + (.summary // "No summary"),
"Description: " + ((.description // "No description") | split("\n")[0]),
""
'
}
# Get operation schema
get_operation_schema() {
local service="$1"
local operation="$2"
[[ -z "$service" || -z "$operation" ]] && {
log_error "Service name and operation required"; usage; exit 1;
}
log_info "Getting schema for $service/$operation"
local auth_header
auth_header=$(get_auth_headers)
local response
if [[ -n "$auth_header" ]]; then
response=$(http GET "$MCPO_HOST/$service/openapi.json" "$auth_header" 2>/dev/null)
else
response=$(http GET "$MCPO_HOST/$service/openapi.json" 2>/dev/null)
fi
if [[ $? -ne 0 ]]; then
log_error "Failed to get schema for service: $service"
exit 1
fi
echo "$response" | jq --arg op "/$operation" '
.paths[$op] | to_entries[] |
.key as $method |
.value |
{
operation: $op,
method: ($method | ascii_upcase),
summary: .summary,
description: .description,
requestBody: (.requestBody.content."application/json".schema // null),
responses: .responses
}
'
}
# Call service operation
call_operation() {
local service="$1"
local operation="$2"
local json_data="$3"
[[ -z "$service" || -z "$operation" ]] && {
log_error "Service name and operation required"; usage; exit 1;
}
log_info "Calling $service/$operation"
local auth_header
auth_header=$(get_auth_headers)
local url="$MCPO_HOST/$service/$operation"
# Build http command
local cmd=(http POST "$url")
if [[ -n "$auth_header" ]]; then
cmd+=("$auth_header")
fi
if [[ -n "$json_data" ]]; then
# Validate JSON
echo "$json_data" | jq . >/dev/null 2>&1 || {
log_error "Invalid JSON data provided"
exit 1
}
cmd+=(--json)
# Use process substitution to pass JSON data
"${cmd[@]}" < <(echo "$json_data")
else
"${cmd[@]}" --json
fi
}
# Main command dispatcher
main() {
case "${1:-}" in
"discover")
discover_services
;;
"describe")
describe_service "$2"
;;
"operations")
list_operations "$2"
;;
"schema")
get_operation_schema "$2" "$3"
;;
"call")
call_operation "$2" "$3" "$4"
;;
"-h"|"--help"|"help"|"")
usage
;;
*)
log_error "Unknown command: $1"
usage
exit 1
;;
esac
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment