-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathssh-mcp.sh
executable file
·211 lines (179 loc) · 5.45 KB
/
ssh-mcp.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#!/bin/bash
# ssh-mcp: Smart client for Machine Chat Protocol over SSH
# Version: 0.2.0
set -e
SSH_CONFIG="$HOME/.ssh/config"
INTERACTIVE=true
# Function to show help
show_help() {
echo "Usage: ssh-mcp [OPTIONS] COMMAND [ARGS]"
echo ""
echo "Options:"
echo " --server HOST Specify remote server (user@host)"
echo " --key PATH SSH private key path"
echo " --non-interactive Disable interactive host selection"
echo ""
echo "Commands:"
echo " --list List available tools"
echo " --describe TOOL Show tool description"
echo " TOOL [JSON_ARGS] Execute tool with arguments"
echo ""
echo "Hosts are read from ~/.ssh/config"
exit 1
}
# Function to list and select hosts from SSH config
select_host() {
if [ ! -f "$SSH_CONFIG" ]; then
echo "No SSH config file found at ~/.ssh/config"
exit 1
fi
# Get hosts from SSH config (excluding wildcards and patterns)
HOSTS=$(grep -i "^Host " "$SSH_CONFIG" | grep -v "[*?]" | awk '{print $2}' | sort)
if [ -z "$HOSTS" ]; then
echo "No hosts found in SSH config."
exit 1
fi
echo "Available hosts from SSH config:"
echo "------------------------------"
i=1
while IFS= read -r host; do
# Get user if specified in config
USER=$(awk "/^Host $host\$/,/^$/ {if (\$1 == \"User\") print \$2}" "$SSH_CONFIG")
# Get identity file if specified
IDENTITY=$(awk "/^Host $host\$/,/^$/ {if (\$1 == \"IdentityFile\") print \$2}" "$SSH_CONFIG")
if [ -n "$USER" ]; then
echo "$i) $host ($USER)"
else
echo "$i) $host"
fi
i=$((i+1))
done <<< "$HOSTS"
echo "q) Quit"
echo ""
read -p "Select host (1-n): " choice
if [[ $choice == "q" ]]; then
exit 0
fi
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "$((i-1))" ]; then
echo "Invalid selection"
exit 1
fi
# Get selected host
SELECTED_HOST=$(echo "$HOSTS" | sed -n "${choice}p")
# Get configuration for selected host
USER=$(awk "/^Host $SELECTED_HOST\$/,/^$/ {if (\$1 == \"User\") print \$2}" "$SSH_CONFIG")
IDENTITY=$(awk "/^Host $SELECTED_HOST\$/,/^$/ {if (\$1 == \"IdentityFile\") print \$2}" "$SSH_CONFIG")
# Expand ~ in identity file path
IDENTITY="${IDENTITY/#\~/$HOME}"
if [ -n "$USER" ]; then
REMOTE_SERVER="$USER@$SELECTED_HOST"
else
REMOTE_SERVER="$SELECTED_HOST"
fi
if [ -n "$IDENTITY" ]; then
SSH_KEY="$IDENTITY"
fi
REMOTE_PATH="~/.ssh-mcp"
}
# Check for help first
case "$1" in
--help|-h)
show_help
;;
esac
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--server)
REMOTE_SERVER="$2"
INTERACTIVE=false
shift 2
;;
--key)
SSH_KEY="$2"
INTERACTIVE=false
shift 2
;;
--non-interactive)
INTERACTIVE=false
shift
;;
*)
break
;;
esac
done
# If in interactive mode and no server specified, show host selection
if [ "$INTERACTIVE" = true ] && [ -z "$REMOTE_SERVER" ]; then
select_host
fi
# Validate required parameters
if [ -z "$REMOTE_SERVER" ]; then
echo "Error: Remote server not specified"
echo "Use --server option or select a host from SSH config"
exit 1
fi
# Build SSH command
SSH_CMD="ssh"
if [ -n "$SSH_KEY" ]; then
SSH_CMD="ssh -i $SSH_KEY"
fi
# Function to execute remote command
remote_exec() {
# First check if jq is available
if ! $SSH_CMD "$REMOTE_SERVER" "command -v jq >/dev/null 2>&1"; then
echo "Warning: jq not found on remote host. Using alternative JSON processing..."
# Use grep and sed for basic JSON processing
$SSH_CMD "$REMOTE_SERVER" "cd $REMOTE_PATH && $1" | sed 's/\\n/\n/g' | sed 's/\\t/\t/g'
else
$SSH_CMD "$REMOTE_SERVER" "cd $REMOTE_PATH && $1"
fi
}
# If first arg is empty and input is JSON, parse host/tool/args from stdin
if [[ -z "$1" && ! -t 0 ]]; then
MCP_INPUT=$(cat)
HOST=$(echo "$MCP_INPUT" | jq -r '.host // empty')
TOOL=$(echo "$MCP_INPUT" | jq -r '.tool // empty')
ARGS=$(echo "$MCP_INPUT" | jq -c '.args // {}')
if [[ -z "$HOST" || -z "$TOOL" ]]; then
echo "Error: Missing host or tool in input JSON"
exit 1
fi
INTERACTIVE=false
# Get user/identity manually
USER=$(awk "/^Host $HOST\$/,/^$/ {if (\$1 == \"User\") print \$2}" "$SSH_CONFIG")
IDENTITY=$(awk "/^Host $HOST\$/,/^$/ {if (\$1 == \"IdentityFile\") print \$2}" "$SSH_CONFIG")
IDENTITY="${IDENTITY/#\~/$HOME}"
REMOTE_SERVER="${USER:+$USER@}$HOST"
SSH_CMD="ssh"
[ -n "$IDENTITY" ] && SSH_CMD="ssh -i $IDENTITY"
echo "$MCP_INPUT" | remote_exec "./mcp.sh"
exit $?
fi
# Handle commands
case "$1" in
--list)
remote_exec "./mcp.sh --list"
exit $?
;;
--describe)
if [ -z "$2" ]; then
echo "Usage: ssh-mcp --describe TOOL"
exit 1
fi
remote_exec "./mcp.sh --describe $2"
exit $?
;;
"")
echo "Error: No command specified"
echo "Try 'ssh-mcp --help' for usage information"
exit 1
;;
*)
# Tool execution with arguments
TOOL="$1"
ARGS="${2:-{}}"
echo "$ARGS" | remote_exec "./mcp.sh $TOOL"
exit $?
;;
esac