Skip to content

Commit 4ff303a

Browse files
adileiadileiclaude
authored
Improve deploy script: colored output, name conflict handling, fixed paths (#482)
- Numbered steps with colored status indicators - Server logs redirected to /tmp/ for clean output - Auto-retry with random suffix on connector name conflict - Fix paths for sample directory structure (connector/ not mcp-connector/) - Use built JS instead of ts-node - Require environment ID as argument (no hardcoded default) Co-authored-by: adilei <adileibowiz@microsoft.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 49c2504 commit 4ff303a

File tree

1 file changed

+165
-100
lines changed
  • extensibility/mcp/dynamic-mcp-routing-typescript/scripts

1 file changed

+165
-100
lines changed
Lines changed: 165 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
#!/usr/bin/env bash
2-
#
3-
# deploy.sh — Start servers, create a dev tunnel, and deploy/update
4-
# the Power Platform connector in one step.
5-
#
6-
# Usage:
7-
# ./scripts/deploy.sh [ENVIRONMENT_ID] [TENANT_ID]
8-
#
9-
set -euo pipefail
2+
set -uo pipefail
103

4+
# --- Configuration ---
115
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
126
CATALOG_PORT=3000
137
MCP_PORT=3001
@@ -19,91 +13,123 @@ PROPS_FILE="$PROJECT_DIR/connector/apiProperties.json"
1913
SCRIPT_FILE="$PROJECT_DIR/connector/script.csx"
2014
PACONN_TOKEN_FILE="$HOME/.paconn/accessTokens.json"
2115

22-
# --- 0. Ensure paconn is logged in (correct tenant) ---
23-
ensure_login() {
24-
local need_login=false
16+
# --- Output helpers ---
17+
BOLD="\033[1m"
18+
DIM="\033[2m"
19+
GREEN="\033[32m"
20+
YELLOW="\033[33m"
21+
RED="\033[31m"
22+
CYAN="\033[36m"
23+
RESET="\033[0m"
24+
25+
step() { echo -e "\n${BOLD}${CYAN}[$1]${RESET} ${BOLD}$2${RESET}"; }
26+
info() { echo -e " ${DIM}$1${RESET}"; }
27+
ok() { echo -e " ${GREEN}${RESET} $1"; }
28+
warn() { echo -e " ${YELLOW}${RESET} $1"; }
29+
fail() { echo -e " ${RED}${RESET} $1"; }
30+
31+
# --- Cleanup ---
32+
cleanup() {
33+
echo ""
34+
step "" "Shutting down..."
35+
[ -n "${CATALOG_PID:-}" ] && kill "$CATALOG_PID" 2>/dev/null
36+
[ -n "${MCP_PID:-}" ] && kill "$MCP_PID" 2>/dev/null
37+
[ -n "${TUNNEL_PID:-}" ] && kill "$TUNNEL_PID" 2>/dev/null
38+
wait 2>/dev/null
39+
ok "All processes stopped."
40+
}
41+
trap cleanup EXIT
42+
43+
# ============================================================
44+
# Step 1: Authentication
45+
# ============================================================
46+
step "1/5" "Authenticating with Power Platform"
2547

26-
if [ ! -f "$PACONN_TOKEN_FILE" ]; then
27-
echo " No token file found."
48+
need_login=false
49+
if [ ! -f "$PACONN_TOKEN_FILE" ]; then
50+
info "No token file found."
51+
need_login=true
52+
else
53+
expires_on=$(python3 -c "import json; print(json.load(open('$PACONN_TOKEN_FILE')).get('expires_on','0'))" 2>/dev/null || echo "0")
54+
now=$(python3 -c "import time; print(time.time())")
55+
if python3 -c "exit(0 if float('$expires_on') < float('$now') else 1)" 2>/dev/null; then
56+
info "Token expired."
2857
need_login=true
29-
else
30-
local expires_on
31-
expires_on=$(python3 -c "import json; print(json.load(open('$PACONN_TOKEN_FILE')).get('expires_on','0'))" 2>/dev/null || echo "0")
32-
local now
33-
now=$(python3 -c "import time; print(time.time())")
34-
if python3 -c "exit(0 if float('$expires_on') < float('$now') else 1)" 2>/dev/null; then
35-
echo " Token expired."
36-
need_login=true
37-
fi
58+
fi
3859

39-
if [ -n "$TENANT_ID" ] && [ "$need_login" = false ]; then
40-
local current_tenant
41-
current_tenant=$(python3 -c "import json; print(json.load(open('$PACONN_TOKEN_FILE')).get('tenant_id',''))" 2>/dev/null || echo "")
42-
if [ "$current_tenant" != "$TENANT_ID" ]; then
43-
echo " Logged into tenant $current_tenant, need $TENANT_ID."
44-
need_login=true
45-
fi
60+
if [ -n "$TENANT_ID" ] && [ "$need_login" = false ]; then
61+
current_tenant=$(python3 -c "import json; print(json.load(open('$PACONN_TOKEN_FILE')).get('tenant_id',''))" 2>/dev/null || echo "")
62+
if [ "$current_tenant" != "$TENANT_ID" ]; then
63+
info "Logged into tenant $current_tenant, need $TENANT_ID."
64+
need_login=true
4665
fi
4766
fi
67+
fi
4868

49-
if [ "$need_login" = true ]; then
50-
echo " Logging in..."
51-
if [ -n "$TENANT_ID" ]; then
52-
python3 -m paconn login -t "$TENANT_ID"
53-
else
54-
python3 -m paconn login
55-
fi
56-
echo " Login complete."
69+
if [ "$need_login" = true ]; then
70+
warn "Login required — follow the device code prompt below:"
71+
if [ -n "$TENANT_ID" ]; then
72+
python3 -m paconn login -t "$TENANT_ID"
5773
else
58-
local user_id
59-
user_id=$(python3 -c "import json; print(json.load(open('$PACONN_TOKEN_FILE')).get('user_id','unknown'))" 2>/dev/null)
60-
echo " Logged in as $user_id"
74+
python3 -m paconn login
6175
fi
62-
}
76+
ok "Login complete."
77+
else
78+
user_id=$(python3 -c "import json; print(json.load(open('$PACONN_TOKEN_FILE')).get('user_id','unknown'))" 2>/dev/null)
79+
ok "Logged in as $user_id"
80+
fi
6381

64-
echo "==> Checking paconn login..."
65-
ensure_login
82+
# ============================================================
83+
# Step 2: Start servers
84+
# ============================================================
85+
step "2/5" "Starting servers"
6686

67-
# --- Helpers ---
68-
cleanup() {
69-
echo ""
70-
echo "Shutting down..."
71-
[ -n "${CATALOG_PID:-}" ] && kill "$CATALOG_PID" 2>/dev/null
72-
[ -n "${MCP_PID:-}" ] && kill "$MCP_PID" 2>/dev/null
73-
[ -n "${TUNNEL_PID:-}" ] && kill "$TUNNEL_PID" 2>/dev/null
74-
wait 2>/dev/null
75-
echo "Done."
76-
}
77-
trap cleanup EXIT
78-
79-
# --- 1. Build & start servers ---
80-
echo "==> Building..."
87+
info "Building..."
8188
cd "$PROJECT_DIR"
82-
npm run build
89+
npm run build > /dev/null 2>&1
8390

84-
echo "==> Starting catalog server (port $CATALOG_PORT)..."
85-
PORT=$CATALOG_PORT node build/catalog/index.js &
91+
info "Catalog server on port $CATALOG_PORT..."
92+
PORT=$CATALOG_PORT node build/catalog/index.js > /tmp/catalog-server.log 2>&1 &
8693
CATALOG_PID=$!
8794

88-
echo "==> Starting MCP server (port $MCP_PORT)..."
89-
PORT=$MCP_PORT node build/mcp-server/index.js &
95+
info "MCP server on port $MCP_PORT..."
96+
PORT=$MCP_PORT node build/mcp-server/index.js > /tmp/mcp-server.log 2>&1 &
9097
MCP_PID=$!
9198

92-
echo " Waiting for servers..."
93-
for i in $(seq 1 15); do
94-
if curl -sf http://localhost:$CATALOG_PORT/instances > /dev/null 2>&1; then
95-
echo " Both servers ready."
99+
info "Waiting for servers to respond..."
100+
SERVERS_READY=false
101+
for i in $(seq 1 20); do
102+
catalog_ok=false
103+
mcp_ok=false
104+
curl -sf http://localhost:$CATALOG_PORT/instances > /dev/null 2>&1 && catalog_ok=true
105+
curl -sf -o /dev/null -w '' http://localhost:$MCP_PORT/instances/contoso/mcp 2>/dev/null && mcp_ok=true
106+
# Also accept connection refused → not ready yet; 404/405 → server is up
107+
curl -s -o /dev/null -w "%{http_code}" http://localhost:$MCP_PORT/ 2>/dev/null | grep -qE "^[2-5]" && mcp_ok=true
108+
if [ "$catalog_ok" = true ] && [ "$mcp_ok" = true ]; then
109+
SERVERS_READY=true
96110
break
97111
fi
98112
sleep 1
99113
done
114+
if [ "$SERVERS_READY" = true ]; then
115+
ok "Catalog server ready on :$CATALOG_PORT"
116+
ok "MCP server ready on :$MCP_PORT"
117+
else
118+
fail "Servers did not start in time."
119+
info "Catalog log: /tmp/catalog-server.log"
120+
info "MCP log: /tmp/mcp-server.log"
121+
exit 1
122+
fi
123+
124+
# ============================================================
125+
# Step 3: Dev tunnel
126+
# ============================================================
127+
step "3/5" "Creating dev tunnel"
100128

101-
# --- 2. Start devtunnel ---
102-
echo "==> Starting devtunnel for ports $CATALOG_PORT and $MCP_PORT..."
129+
info "Exposing ports $CATALOG_PORT and $MCP_PORT..."
103130
devtunnel host -p "$CATALOG_PORT" -p "$MCP_PORT" --allow-anonymous > /tmp/devtunnel-output.log 2>&1 &
104131
TUNNEL_PID=$!
105132

106-
echo " Waiting for tunnel..."
107133
CATALOG_HOST=""
108134
MCP_HOST=""
109135
for i in $(seq 1 30); do
@@ -112,71 +138,110 @@ for i in $(seq 1 30); do
112138
MCP_HOST=$(grep -oE "[a-z0-9]+-${MCP_PORT}\.[a-z]+\.devtunnels\.ms" /tmp/devtunnel-output.log | head -1)
113139
break
114140
fi
141+
if [ "$i" -eq 30 ]; then
142+
fail "Tunnel did not start in time."
143+
info "Log: /tmp/devtunnel-output.log"
144+
exit 1
145+
fi
115146
sleep 1
116147
done
117148

118-
if [ -z "$CATALOG_HOST" ] || [ -z "$MCP_HOST" ]; then
119-
echo "ERROR: Could not extract tunnel URLs. Log:"
120-
cat /tmp/devtunnel-output.log
121-
exit 1
122-
fi
123-
124-
echo " Tunnel ready!"
125-
echo " Catalog: https://$CATALOG_HOST"
126-
echo " MCP: https://$MCP_HOST"
149+
ok "Catalog: https://$CATALOG_HOST"
150+
ok "MCP: https://$MCP_HOST"
127151

128-
# --- 3. Restart catalog with tunnel URL ---
152+
# Restart catalog with public MCP URL
153+
info "Restarting catalog with public MCP URLs..."
129154
kill "$CATALOG_PID" 2>/dev/null
130155
wait "$CATALOG_PID" 2>/dev/null || true
131-
echo "==> Restarting catalog with MCP_SERVER_BASE=https://$MCP_HOST..."
132-
MCP_SERVER_BASE="https://$MCP_HOST" PORT=$CATALOG_PORT node build/catalog/index.js &
156+
cd "$PROJECT_DIR"
157+
MCP_SERVER_BASE="https://$MCP_HOST" PORT=$CATALOG_PORT node build/catalog/index.js > /tmp/catalog-server.log 2>&1 &
133158
CATALOG_PID=$!
134159
sleep 3
135160

136-
echo " Verifying catalog..."
137-
curl -s "https://$CATALOG_HOST/instances" | head -c 200
138-
echo ""
161+
# Verify
162+
INSTANCE_COUNT=$(curl -s "https://$CATALOG_HOST/instances" 2>/dev/null | python3 -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
163+
ok "Catalog verified — $INSTANCE_COUNT instances with public MCP URLs"
164+
165+
# ============================================================
166+
# Step 4: Update swagger
167+
# ============================================================
168+
step "4/5" "Updating connector definition"
139169

140-
# --- 4. Update swagger host ---
141-
echo "==> Updating swagger host to $CATALOG_HOST..."
142170
python3 -c "
143171
import json
144172
with open('$SWAGGER_FILE', 'r') as f:
145173
swagger = json.load(f)
146174
swagger['host'] = '$CATALOG_HOST'
147175
with open('$SWAGGER_FILE', 'w') as f:
148176
json.dump(swagger, f, indent=2)
149-
print(' Updated to:', swagger['host'])
150177
"
178+
ok "Swagger host → $CATALOG_HOST"
179+
180+
# ============================================================
181+
# Step 5: Deploy connector
182+
# ============================================================
183+
step "5/5" "Deploying connector to environment $ENV_ID"
151184

152-
# --- 5. Deploy or update connector ---
153185
if [ -f "$SETTINGS_FILE" ]; then
154186
CONNECTOR_ID=$(python3 -c "import json; print(json.load(open('$SETTINGS_FILE'))['connectorId'])")
155-
echo "==> Updating existing connector: $CONNECTOR_ID"
187+
info "Found existing connector: $CONNECTOR_ID"
188+
info "Updating..."
156189
python3 -m paconn update \
157190
-e "$ENV_ID" \
158191
-c "$CONNECTOR_ID" \
159192
-d "$SWAGGER_FILE" \
160193
-p "$PROPS_FILE" \
161194
-x "$SCRIPT_FILE"
195+
ok "Connector updated."
162196
else
163-
echo "==> Creating new connector..."
164-
python3 -m paconn create \
197+
info "No settings.json found — creating new connector..."
198+
199+
CREATE_OUTPUT=$(python3 -m paconn create \
165200
-e "$ENV_ID" \
166201
-d "$SWAGGER_FILE" \
167202
-p "$PROPS_FILE" \
168203
-x "$SCRIPT_FILE" \
169-
-w
170-
echo " Connector created."
204+
-w 2>&1) || true
205+
206+
if echo "$CREATE_OUTPUT" | grep -qi "DisplayNameIsInUse\|already exists"; then
207+
SUFFIX=$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 4)
208+
NEW_TITLE="Dynamic MCP Connector $SUFFIX"
209+
warn "Name already taken. Retrying as '$NEW_TITLE'..."
210+
python3 -c "
211+
import json
212+
with open('$SWAGGER_FILE', 'r') as f:
213+
swagger = json.load(f)
214+
swagger['info']['title'] = '$NEW_TITLE'
215+
with open('$SWAGGER_FILE', 'w') as f:
216+
json.dump(swagger, f, indent=2)
217+
"
218+
python3 -m paconn create \
219+
-e "$ENV_ID" \
220+
-d "$SWAGGER_FILE" \
221+
-p "$PROPS_FILE" \
222+
-x "$SCRIPT_FILE" \
223+
-w
224+
ok "Connector '$NEW_TITLE' created."
225+
elif echo "$CREATE_OUTPUT" | grep -qi "created successfully"; then
226+
ok "Connector created."
227+
else
228+
echo "$CREATE_OUTPUT"
229+
fail "Connector creation failed. See output above."
230+
exit 1
231+
fi
171232
fi
172233

234+
# ============================================================
235+
# Done
236+
# ============================================================
173237
echo ""
174-
echo "========================================="
175-
echo " Deployment complete!"
176-
echo " Catalog: https://$CATALOG_HOST"
177-
echo " MCP: https://$MCP_HOST"
178-
echo " Environment: $ENV_ID"
179-
echo "========================================="
238+
echo -e "${BOLD}${GREEN}=========================================${RESET}"
239+
echo -e "${BOLD} Deployment complete!${RESET}"
240+
echo -e "${GREEN}=========================================${RESET}"
241+
echo -e " Catalog: ${CYAN}https://$CATALOG_HOST${RESET}"
242+
echo -e " MCP Server: ${CYAN}https://$MCP_HOST${RESET}"
243+
echo -e " Environment: ${DIM}$ENV_ID${RESET}"
244+
echo -e "${GREEN}=========================================${RESET}"
180245
echo ""
181-
echo "Press Ctrl+C to stop servers and tunnel."
246+
echo -e "${DIM}Press Ctrl+C to stop servers and tunnel.${RESET}"
182247
wait

0 commit comments

Comments
 (0)