@programinglive/dev-workflow-mcp-server 1.7.10 โ 1.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +60 -0
- package/.env.example +12 -0
- package/.env.production +16 -0
- package/CHANGELOG.md +45 -0
- package/Dockerfile +32 -0
- package/README.md +63 -2
- package/db/adapters/mysql.js +11 -4
- package/db/migrations/002_create_users_table.sql +22 -0
- package/db/migrations/003_create_workflow_history.sql +14 -0
- package/db/migrations/004_fix_workflow_history.sql +3 -0
- package/docker-compose.yml +24 -0
- package/docs/GCP_DEPLOYMENT.md +298 -0
- package/docs/product/PRD.md +5 -1
- package/docs/release-notes/RELEASE_NOTES.md +26 -0
- package/index.js +2 -0
- package/package.json +9 -3
- package/scripts/add-user.js +52 -0
- package/scripts/create-admin.js +47 -0
- package/scripts/deploy-gcloud.ps1 +160 -0
- package/scripts/deploy-gcloud.sh +154 -0
- package/tests/db-adapters.test.js +108 -0
- package/tests/env-support.test.js +46 -0
- package/tests/workflowState.test.js +16 -1
- package/web/__tests__/api/workflow-history.test.js +61 -0
- package/web/app/docs/page.tsx +1 -1
- package/web/app/history/page.tsx +213 -0
- package/web/app/layout.tsx +1 -1
- package/web/app/login/page.tsx +144 -0
- package/web/app/page.tsx +0 -2
- package/web/components/Hero.tsx +1 -1
- package/web/components/History.tsx +63 -16
- package/web/components/Navbar.tsx +111 -9
- package/web/lib/auth.js +75 -0
- package/web/middleware/auth.js +26 -0
- package/web/next.config.ts +4 -0
- package/web/package-lock.json +40 -40
- package/web/package.json +6 -5
- package/web/scripts/smoke-test.js +30 -0
- package/web/server.js +163 -18
|
@@ -4,6 +4,8 @@ Authoritative history of user-visible changes for the Development Workflow MCP S
|
|
|
4
4
|
|
|
5
5
|
| Version | Date | Notes |
|
|
6
6
|
|---------|------|-------|
|
|
7
|
+
| 1.7.11 | 2025-12-17 | test: comprehensive DB adapter tests, env support, and warning fixes (64b029a) |
|
|
8
|
+
| 1.7.10 | 2025-12-16 | update release notes and finalize ci config (92ac78a) |
|
|
7
9
|
| 1.7.9 | 2025-12-16 | **ci:** apply official npm trusted publishing config (9256583) |
|
|
8
10
|
| 1.7.8 | 2025-12-16 | **ci:** remove all token config for pure OIDC publishing (d4759cc) |
|
|
9
11
|
| 1.7.7 | 2025-12-16 | **ci:** remove duplicates and apply robust trusted publishing config (eedb661) |
|
|
@@ -31,6 +33,30 @@ Authoritative history of user-visible changes for the Development Workflow MCP S
|
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## 1.7.11 โ ๐ Documentation
|
|
39
|
+
|
|
40
|
+
Released on **2025-12-16**.
|
|
41
|
+
|
|
42
|
+
- fix version status in PRD (fa7912b)
|
|
43
|
+
- implement .env support and fix mysql date persistence (096797c)
|
|
44
|
+
## 1.7.11 โ ๐ Documentation & ๐งช Testing
|
|
45
|
+
|
|
46
|
+
Released on **2025-12-17**.
|
|
47
|
+
|
|
48
|
+
- test: comprehensive DB adapter tests for MySQL, PostgreSQL, and SQLite (64b029a)
|
|
49
|
+
- test: implemented env support verification (64b029a)
|
|
50
|
+
- test: fixed deprecation warnings and missing mock exports (64b029a)
|
|
51
|
+
- docs: updated PRD and README with testing guide (64b029a)
|
|
52
|
+
|
|
53
|
+
## 1.7.10 โ ๐งน Chores
|
|
54
|
+
|
|
55
|
+
Released on **2025-12-16**.
|
|
56
|
+
|
|
57
|
+
- update release notes and finalize ci config (92ac78a)
|
|
58
|
+
- update PRD and release notes for v1.7.9 (024b3c2)
|
|
59
|
+
|
|
34
60
|
## 1.7.9 โ ๐ Bug Fixes
|
|
35
61
|
|
|
36
62
|
Released on **2025-12-16**.
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.12",
|
|
4
4
|
"description": "MCP server to enforce development workflow discipline",
|
|
5
5
|
"homepage": "https://devworkflow.programinglive.com/",
|
|
6
6
|
"mcpName": "io.github.programinglive/dev-workflow-mcp-server",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dev": "node --watch index.js",
|
|
18
18
|
"start": "node index.js",
|
|
19
19
|
"local": "node index.js",
|
|
20
|
-
"test": "node --test",
|
|
20
|
+
"test": "node --no-deprecation --test",
|
|
21
21
|
"check": "node --check index.js",
|
|
22
22
|
"prepare": "husky",
|
|
23
23
|
"postinstall": "node install.js",
|
|
@@ -42,13 +42,19 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
45
|
+
"bcrypt": "^5.1.1",
|
|
45
46
|
"better-sqlite3": "^11.8.0",
|
|
47
|
+
"connect-pg-simple": "^9.0.1",
|
|
48
|
+
"dotenv": "^17.2.3",
|
|
46
49
|
"esbuild": "^0.25.0",
|
|
50
|
+
"express": "^4.18.2",
|
|
51
|
+
"express-session": "^1.18.0",
|
|
47
52
|
"get-port": "^7.1.0",
|
|
48
53
|
"marked": "^12.0.2",
|
|
49
54
|
"mysql2": "^3.15.3",
|
|
50
55
|
"pg": "^8.16.3",
|
|
51
|
-
"vite": "^7.1.12"
|
|
56
|
+
"vite": "^7.1.12",
|
|
57
|
+
"ws": "^8.16.0"
|
|
52
58
|
},
|
|
53
59
|
"publishConfig": {
|
|
54
60
|
"access": "public"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import pg from 'pg';
|
|
4
|
+
import { createUser } from '../web/lib/auth.js';
|
|
5
|
+
|
|
6
|
+
const { Pool } = pg;
|
|
7
|
+
|
|
8
|
+
// Get database configuration from environment
|
|
9
|
+
const dbUrl = process.env.DEV_WORKFLOW_DB_URL;
|
|
10
|
+
|
|
11
|
+
if (!dbUrl) {
|
|
12
|
+
console.error('Error: DEV_WORKFLOW_DB_URL environment variable is not set');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const pool = new Pool({ connectionString: dbUrl });
|
|
17
|
+
|
|
18
|
+
async function addUser() {
|
|
19
|
+
const username = process.argv[2];
|
|
20
|
+
const password = process.argv[3];
|
|
21
|
+
const email = process.argv[4];
|
|
22
|
+
|
|
23
|
+
if (!username || !password) {
|
|
24
|
+
console.error('Usage: node scripts/add-user.js <username> <password> [email]');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Check if user already exists
|
|
30
|
+
const existing = await pool.query('SELECT id FROM users WHERE username = $1', [username]);
|
|
31
|
+
|
|
32
|
+
if (existing.rows.length > 0) {
|
|
33
|
+
console.log(`โ User '${username}' already exists`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Create user
|
|
38
|
+
const user = await createUser(pool, username, password, email);
|
|
39
|
+
console.log(`โ Created user: ${user.username}`);
|
|
40
|
+
if (user.email) {
|
|
41
|
+
console.log(` Email: ${user.email}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Error creating user:', error.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
} finally {
|
|
48
|
+
await pool.end();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
addUser();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import pg from 'pg';
|
|
4
|
+
import { createUser } from '../web/lib/auth.js';
|
|
5
|
+
|
|
6
|
+
const { Pool } = pg;
|
|
7
|
+
|
|
8
|
+
// Get database configuration from environment
|
|
9
|
+
const dbUrl = process.env.DEV_WORKFLOW_DB_URL;
|
|
10
|
+
|
|
11
|
+
if (!dbUrl) {
|
|
12
|
+
console.error('Error: DEV_WORKFLOW_DB_URL environment variable is not set');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const pool = new Pool({ connectionString: dbUrl });
|
|
17
|
+
|
|
18
|
+
async function createAdminUser() {
|
|
19
|
+
const username = process.env.ADMIN_USERNAME || 'admin';
|
|
20
|
+
const password = process.env.ADMIN_PASSWORD || 'admin123';
|
|
21
|
+
const email = process.env.ADMIN_EMAIL || 'admin@example.com';
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Check if admin user already exists
|
|
25
|
+
const existing = await pool.query('SELECT id FROM users WHERE username = $1', [username]);
|
|
26
|
+
|
|
27
|
+
if (existing.rows.length > 0) {
|
|
28
|
+
console.log(`โ Admin user '${username}' already exists`);
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Create admin user
|
|
33
|
+
const user = await createUser(pool, username, password, email);
|
|
34
|
+
console.log(`โ Created admin user: ${user.username}`);
|
|
35
|
+
console.log(` Email: ${user.email}`);
|
|
36
|
+
console.log(` Password: ${password}`);
|
|
37
|
+
console.log('\nโ ๏ธ Please change the password after first login!');
|
|
38
|
+
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error creating admin user:', error.message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
} finally {
|
|
43
|
+
await pool.end();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
createAdminUser();
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# GCP Deployment Script using gcloud CLI (PowerShell)
|
|
2
|
+
# This script deploys the dev-workflow MCP server to a GCP Compute Engine instance
|
|
3
|
+
|
|
4
|
+
$ErrorActionPreference = "Stop"
|
|
5
|
+
|
|
6
|
+
# Configuration - Update these values
|
|
7
|
+
$PROJECT_ID = if ($env:GCP_PROJECT_ID) { $env:GCP_PROJECT_ID } else { "edmee-project" }
|
|
8
|
+
$INSTANCE_NAME = if ($env:GCP_INSTANCE_NAME) { $env:GCP_INSTANCE_NAME } else { "docker-almi" }
|
|
9
|
+
$ZONE = if ($env:GCP_ZONE) { $env:GCP_ZONE } else { "asia-southeast2-a" }
|
|
10
|
+
$INSTANCE_USER = if ($env:GCP_INSTANCE_USER) { $env:GCP_INSTANCE_USER } else { "dhika" }
|
|
11
|
+
|
|
12
|
+
Write-Host "๐ Deploying dev-workflow MCP server to GCP..." -ForegroundColor Cyan
|
|
13
|
+
Write-Host "Project: $PROJECT_ID"
|
|
14
|
+
Write-Host "Instance: $INSTANCE_NAME"
|
|
15
|
+
Write-Host "Zone: $ZONE"
|
|
16
|
+
Write-Host ""
|
|
17
|
+
|
|
18
|
+
# Check if gcloud is installed
|
|
19
|
+
if (!(Get-Command gcloud -ErrorAction SilentlyContinue)) {
|
|
20
|
+
Write-Host "โ gcloud CLI is not installed" -ForegroundColor Red
|
|
21
|
+
Write-Host "Please install it from: https://cloud.google.com/sdk/docs/install"
|
|
22
|
+
exit 1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Set the project
|
|
26
|
+
Write-Host "๐ Setting GCP project..." -ForegroundColor Yellow
|
|
27
|
+
gcloud config set project $PROJECT_ID
|
|
28
|
+
|
|
29
|
+
# Get instance external IP
|
|
30
|
+
Write-Host "๐ Getting instance external IP..." -ForegroundColor Yellow
|
|
31
|
+
$EXTERNAL_IP = gcloud compute instances describe $INSTANCE_NAME `
|
|
32
|
+
--zone=$ZONE `
|
|
33
|
+
--format="get(networkInterfaces[0].accessConfigs[0].natIP)"
|
|
34
|
+
|
|
35
|
+
if ([string]::IsNullOrEmpty($EXTERNAL_IP)) {
|
|
36
|
+
Write-Host "โ Could not get instance external IP" -ForegroundColor Red
|
|
37
|
+
exit 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Write-Host "โ
Instance IP: $EXTERNAL_IP" -ForegroundColor Green
|
|
41
|
+
Write-Host ""
|
|
42
|
+
|
|
43
|
+
# Test SSH connection
|
|
44
|
+
Write-Host "๐ Testing SSH connection..." -ForegroundColor Yellow
|
|
45
|
+
try {
|
|
46
|
+
$testResult = gcloud compute ssh "$INSTANCE_USER@$INSTANCE_NAME" `
|
|
47
|
+
--zone=$ZONE `
|
|
48
|
+
--command="echo 'Connection successful'" 2>&1
|
|
49
|
+
Write-Host "โ
SSH connection successful" -ForegroundColor Green
|
|
50
|
+
} catch {
|
|
51
|
+
Write-Host "โ Cannot connect to instance via SSH" -ForegroundColor Red
|
|
52
|
+
Write-Host "Please ensure:"
|
|
53
|
+
Write-Host " 1. SSH keys are configured in GCP"
|
|
54
|
+
Write-Host " 2. Firewall allows SSH (port 22)"
|
|
55
|
+
Write-Host " 3. Instance is running"
|
|
56
|
+
exit 1
|
|
57
|
+
}
|
|
58
|
+
Write-Host ""
|
|
59
|
+
|
|
60
|
+
# Copy files to instance
|
|
61
|
+
Write-Host "๐ฆ Copying project files to instance..." -ForegroundColor Yellow
|
|
62
|
+
gcloud compute scp --recurse `
|
|
63
|
+
--zone=$ZONE `
|
|
64
|
+
. "$INSTANCE_USER@${INSTANCE_NAME}:~/dev-workflow-mcp-server/" `
|
|
65
|
+
--exclude=".git" `
|
|
66
|
+
--exclude="node_modules" `
|
|
67
|
+
--exclude=".state" `
|
|
68
|
+
--exclude="dist"
|
|
69
|
+
|
|
70
|
+
Write-Host "โ
Files copied successfully" -ForegroundColor Green
|
|
71
|
+
Write-Host ""
|
|
72
|
+
|
|
73
|
+
# Create deployment script for remote execution
|
|
74
|
+
$deployScript = @'
|
|
75
|
+
set -e
|
|
76
|
+
|
|
77
|
+
cd ~/dev-workflow-mcp-server
|
|
78
|
+
|
|
79
|
+
echo "๐ฅ Installing Docker if not present..."
|
|
80
|
+
if ! command -v docker &> /dev/null; then
|
|
81
|
+
curl -fsSL https://get.docker.com -o get-docker.sh
|
|
82
|
+
sudo sh get-docker.sh
|
|
83
|
+
sudo usermod -aG docker $USER
|
|
84
|
+
rm get-docker.sh
|
|
85
|
+
echo "โ
Docker installed"
|
|
86
|
+
else
|
|
87
|
+
echo "โ
Docker already installed"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
echo "๐ฅ Installing Docker Compose if not present..."
|
|
91
|
+
if ! command -v docker-compose &> /dev/null; then
|
|
92
|
+
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
|
93
|
+
sudo chmod +x /usr/local/bin/docker-compose
|
|
94
|
+
echo "โ
Docker Compose installed"
|
|
95
|
+
else
|
|
96
|
+
echo "โ
Docker Compose already installed"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
echo "โ๏ธ Configuring environment..."
|
|
100
|
+
if [ ! -f .env ]; then
|
|
101
|
+
cp .env.production .env
|
|
102
|
+
echo "โ ๏ธ Created .env file from template"
|
|
103
|
+
echo "โ ๏ธ Please update the passwords in .env before production use!"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
echo "๐ Stopping existing containers..."
|
|
107
|
+
docker-compose down 2>/dev/null || true
|
|
108
|
+
|
|
109
|
+
echo "๐๏ธ Building Docker images..."
|
|
110
|
+
docker-compose build --no-cache
|
|
111
|
+
|
|
112
|
+
echo "๐ Starting containers..."
|
|
113
|
+
docker-compose up -d
|
|
114
|
+
|
|
115
|
+
echo "โณ Waiting for services to be healthy..."
|
|
116
|
+
sleep 15
|
|
117
|
+
|
|
118
|
+
echo "๐ Checking container status..."
|
|
119
|
+
docker-compose ps
|
|
120
|
+
|
|
121
|
+
echo ""
|
|
122
|
+
echo "โ
Deployment complete!"
|
|
123
|
+
echo ""
|
|
124
|
+
echo "Container logs:"
|
|
125
|
+
docker-compose logs --tail=20
|
|
126
|
+
'@
|
|
127
|
+
|
|
128
|
+
# Run deployment commands on the instance
|
|
129
|
+
Write-Host "๐ Running deployment on instance..." -ForegroundColor Yellow
|
|
130
|
+
$deployScript | gcloud compute ssh "$INSTANCE_USER@$INSTANCE_NAME" `
|
|
131
|
+
--zone=$ZONE `
|
|
132
|
+
--command="bash -s"
|
|
133
|
+
|
|
134
|
+
Write-Host ""
|
|
135
|
+
Write-Host "๐ Deployment successful!" -ForegroundColor Green
|
|
136
|
+
Write-Host ""
|
|
137
|
+
Write-Host "๐ Next steps:" -ForegroundColor Cyan
|
|
138
|
+
Write-Host "1. SSH into instance and update .env with secure passwords:"
|
|
139
|
+
Write-Host " gcloud compute ssh $INSTANCE_USER@$INSTANCE_NAME --zone=$ZONE"
|
|
140
|
+
Write-Host " cd ~/dev-workflow-mcp-server"
|
|
141
|
+
Write-Host " nano .env"
|
|
142
|
+
Write-Host ""
|
|
143
|
+
Write-Host "2. Update your local MCP client config:" -ForegroundColor Cyan
|
|
144
|
+
Write-Host @"
|
|
145
|
+
{
|
|
146
|
+
"mcpServers": {
|
|
147
|
+
"dev-workflow": {
|
|
148
|
+
"command": "gcloud",
|
|
149
|
+
"args": [
|
|
150
|
+
"compute", "ssh", "$INSTANCE_USER@$INSTANCE_NAME",
|
|
151
|
+
"--zone=$ZONE",
|
|
152
|
+
"--command=docker exec -i dev-workflow-mcp node /app/index.js"
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
"@
|
|
158
|
+
Write-Host ""
|
|
159
|
+
Write-Host "3. Test the connection:" -ForegroundColor Cyan
|
|
160
|
+
Write-Host " gcloud compute ssh $INSTANCE_USER@$INSTANCE_NAME --zone=$ZONE --command='docker exec -i dev-workflow-mcp node /app/index.js call get_workflow_status'"
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GCP Deployment Script using gcloud CLI
|
|
3
|
+
# This script deploys the dev-workflow MCP server to a GCP Compute Engine instance
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
# Configuration - Update these values
|
|
8
|
+
PROJECT_ID="${GCP_PROJECT_ID:-edmee-project}"
|
|
9
|
+
INSTANCE_NAME="${GCP_INSTANCE_NAME:-docker-almi}"
|
|
10
|
+
ZONE="${GCP_ZONE:-asia-southeast2-a}"
|
|
11
|
+
INSTANCE_USER="${GCP_INSTANCE_USER:-dhika}"
|
|
12
|
+
|
|
13
|
+
echo "๐ Deploying dev-workflow MCP server to GCP..."
|
|
14
|
+
echo "Project: $PROJECT_ID"
|
|
15
|
+
echo "Instance: $INSTANCE_NAME"
|
|
16
|
+
echo "Zone: $ZONE"
|
|
17
|
+
echo ""
|
|
18
|
+
|
|
19
|
+
# Check if gcloud is installed
|
|
20
|
+
if ! command -v gcloud &> /dev/null; then
|
|
21
|
+
echo "โ gcloud CLI is not installed"
|
|
22
|
+
echo "Please install it from: https://cloud.google.com/sdk/docs/install"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Set the project
|
|
27
|
+
echo "๐ Setting GCP project..."
|
|
28
|
+
gcloud config set project "$PROJECT_ID"
|
|
29
|
+
|
|
30
|
+
# Get instance external IP
|
|
31
|
+
echo "๐ Getting instance external IP..."
|
|
32
|
+
EXTERNAL_IP=$(gcloud compute instances describe "$INSTANCE_NAME" \
|
|
33
|
+
--zone="$ZONE" \
|
|
34
|
+
--format="get(networkInterfaces[0].accessConfigs[0].natIP)")
|
|
35
|
+
|
|
36
|
+
if [ -z "$EXTERNAL_IP" ]; then
|
|
37
|
+
echo "โ Could not get instance external IP"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
echo "โ
Instance IP: $EXTERNAL_IP"
|
|
42
|
+
echo ""
|
|
43
|
+
|
|
44
|
+
# Test SSH connection
|
|
45
|
+
echo "๐ Testing SSH connection..."
|
|
46
|
+
if ! gcloud compute ssh "$INSTANCE_USER@$INSTANCE_NAME" \
|
|
47
|
+
--zone="$ZONE" \
|
|
48
|
+
--command="echo 'Connection successful'" &> /dev/null; then
|
|
49
|
+
echo "โ Cannot connect to instance via SSH"
|
|
50
|
+
echo "Please ensure:"
|
|
51
|
+
echo " 1. SSH keys are configured in GCP"
|
|
52
|
+
echo " 2. Firewall allows SSH (port 22)"
|
|
53
|
+
echo " 3. Instance is running"
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
echo "โ
SSH connection successful"
|
|
57
|
+
echo ""
|
|
58
|
+
|
|
59
|
+
# Copy files to instance
|
|
60
|
+
echo "๐ฆ Copying project files to instance..."
|
|
61
|
+
gcloud compute scp --recurse \
|
|
62
|
+
--zone="$ZONE" \
|
|
63
|
+
. "$INSTANCE_USER@$INSTANCE_NAME:~/dev-workflow-mcp-server/" \
|
|
64
|
+
--exclude=".git" \
|
|
65
|
+
--exclude="node_modules" \
|
|
66
|
+
--exclude=".state" \
|
|
67
|
+
--exclude="dist"
|
|
68
|
+
|
|
69
|
+
echo "โ
Files copied successfully"
|
|
70
|
+
echo ""
|
|
71
|
+
|
|
72
|
+
# Run deployment commands on the instance
|
|
73
|
+
echo "๐ Running deployment on instance..."
|
|
74
|
+
gcloud compute ssh "$INSTANCE_USER@$INSTANCE_NAME" \
|
|
75
|
+
--zone="$ZONE" \
|
|
76
|
+
--command="bash -s" << 'ENDSSH'
|
|
77
|
+
set -e
|
|
78
|
+
|
|
79
|
+
cd ~/dev-workflow-mcp-server
|
|
80
|
+
|
|
81
|
+
echo "๐ฅ Installing Docker if not present..."
|
|
82
|
+
if ! command -v docker &> /dev/null; then
|
|
83
|
+
curl -fsSL https://get.docker.com -o get-docker.sh
|
|
84
|
+
sudo sh get-docker.sh
|
|
85
|
+
sudo usermod -aG docker $USER
|
|
86
|
+
rm get-docker.sh
|
|
87
|
+
echo "โ
Docker installed"
|
|
88
|
+
else
|
|
89
|
+
echo "โ
Docker already installed"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo "๐ฅ Installing Docker Compose if not present..."
|
|
93
|
+
if ! command -v docker-compose &> /dev/null; then
|
|
94
|
+
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
|
95
|
+
sudo chmod +x /usr/local/bin/docker-compose
|
|
96
|
+
echo "โ
Docker Compose installed"
|
|
97
|
+
else
|
|
98
|
+
echo "โ
Docker Compose already installed"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
echo "โ๏ธ Configuring environment..."
|
|
102
|
+
if [ ! -f .env ]; then
|
|
103
|
+
cp .env.production .env
|
|
104
|
+
echo "โ ๏ธ Created .env file from template"
|
|
105
|
+
echo "โ ๏ธ Please update the passwords in .env before production use!"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
echo "๐ Stopping existing containers..."
|
|
109
|
+
docker-compose down 2>/dev/null || true
|
|
110
|
+
|
|
111
|
+
echo "๐๏ธ Building Docker images..."
|
|
112
|
+
docker-compose build --no-cache
|
|
113
|
+
|
|
114
|
+
echo "๐ Starting containers..."
|
|
115
|
+
docker-compose up -d
|
|
116
|
+
|
|
117
|
+
echo "โณ Waiting for services to be healthy..."
|
|
118
|
+
sleep 15
|
|
119
|
+
|
|
120
|
+
echo "๐ Checking container status..."
|
|
121
|
+
docker-compose ps
|
|
122
|
+
|
|
123
|
+
echo ""
|
|
124
|
+
echo "โ
Deployment complete!"
|
|
125
|
+
echo ""
|
|
126
|
+
echo "Container logs:"
|
|
127
|
+
docker-compose logs --tail=20
|
|
128
|
+
ENDSSH
|
|
129
|
+
|
|
130
|
+
echo ""
|
|
131
|
+
echo "๐ Deployment successful!"
|
|
132
|
+
echo ""
|
|
133
|
+
echo "๐ Next steps:"
|
|
134
|
+
echo "1. SSH into instance and update .env with secure passwords:"
|
|
135
|
+
echo " gcloud compute ssh $INSTANCE_USER@$INSTANCE_NAME --zone=$ZONE"
|
|
136
|
+
echo " cd ~/dev-workflow-mcp-server"
|
|
137
|
+
echo " nano .env"
|
|
138
|
+
echo ""
|
|
139
|
+
echo "2. Update your local MCP client config:"
|
|
140
|
+
echo " {"
|
|
141
|
+
echo " \"mcpServers\": {"
|
|
142
|
+
echo " \"dev-workflow\": {"
|
|
143
|
+
echo " \"command\": \"gcloud\","
|
|
144
|
+
echo " \"args\": ["
|
|
145
|
+
echo " \"compute\", \"ssh\", \"$INSTANCE_USER@$INSTANCE_NAME\","
|
|
146
|
+
echo " \"--zone=$ZONE\","
|
|
147
|
+
echo " \"--command=docker exec -i dev-workflow-mcp node /app/index.js\""
|
|
148
|
+
echo " ]"
|
|
149
|
+
echo " }"
|
|
150
|
+
echo " }"
|
|
151
|
+
echo " }"
|
|
152
|
+
echo ""
|
|
153
|
+
echo "3. Test the connection:"
|
|
154
|
+
echo " gcloud compute ssh $INSTANCE_USER@$INSTANCE_NAME --zone=$ZONE --command='docker exec -i dev-workflow-mcp node /app/index.js call get_workflow_status'"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
|
|
8
|
+
import { SqliteAdapter } from "../db/adapters/sqlite.js";
|
|
9
|
+
import { MysqlAdapter } from "../db/adapters/mysql.js";
|
|
10
|
+
import { PostgresAdapter } from "../db/adapters/postgres.js";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Helper to run common adapter tests
|
|
14
|
+
async function runGenericAdapterTests(t, adapter, testUserId, testProject) {
|
|
15
|
+
await t.test("connects and migrates", async () => {
|
|
16
|
+
await adapter.connect();
|
|
17
|
+
// No error means success
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await t.test("inserts and retrieves history", async () => {
|
|
21
|
+
const entry = {
|
|
22
|
+
taskDescription: "Test Task",
|
|
23
|
+
taskType: "feature",
|
|
24
|
+
commitMessage: "feat: test",
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
await adapter.insertHistoryEntry(testUserId, testProject, entry);
|
|
28
|
+
|
|
29
|
+
const history = await adapter.getHistoryForUser(testUserId, testProject, { pageSize: 1 });
|
|
30
|
+
assert.equal(history.entries.length, 1);
|
|
31
|
+
assert.equal(history.entries[0].task_description, "Test Task");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await t.test("saves and retrieves state", async () => {
|
|
35
|
+
const state = { phase: "testing", count: 123 };
|
|
36
|
+
await adapter.saveState(testUserId, testProject, state);
|
|
37
|
+
const retrieved = await adapter.getState(testUserId, testProject);
|
|
38
|
+
assert.deepEqual(retrieved, state);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await t.test("history summary aggregation", async () => {
|
|
42
|
+
// already inserted one entry
|
|
43
|
+
const summary = await adapter.getHistorySummary(testUserId, testProject, { frequency: 'daily' });
|
|
44
|
+
assert.ok(summary.length > 0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await t.test("project summary update", async () => {
|
|
48
|
+
const summary = await adapter.getSummaryForUser(testUserId, testProject);
|
|
49
|
+
assert.ok(summary);
|
|
50
|
+
assert.ok(summary.totalTasks >= 1);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test("Database Adapters", async (t) => {
|
|
55
|
+
// 1. SQLite (Always Test)
|
|
56
|
+
await t.test("SqliteAdapter", async (t) => {
|
|
57
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "sqlite-test-"));
|
|
58
|
+
const dbPath = path.join(tmpDir, "test.db");
|
|
59
|
+
|
|
60
|
+
// Convert to file URI for connection string if needed, or just pass path
|
|
61
|
+
// The adapter expects a connection URL for SQLite usually: file:///path/to/db or just path?
|
|
62
|
+
// Looking at sqlite.js: uri: this.config.connectionUrl.replace("sqlite://", "")
|
|
63
|
+
// So we invoke it with sqlite://<path>
|
|
64
|
+
const connectionUrl = `sqlite://${dbPath}`;
|
|
65
|
+
|
|
66
|
+
const adapter = new SqliteAdapter({ connectionUrl });
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await runGenericAdapterTests(t, adapter, "user-sqlite", tmpDir);
|
|
70
|
+
} finally {
|
|
71
|
+
if (adapter.db) adapter.db.close();
|
|
72
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 2. MySQL (Conditional)
|
|
77
|
+
// Check specific test variable OR generic one if it maps to mysql
|
|
78
|
+
const mysqlUrl = process.env.TEST_MYSQL_URL ||
|
|
79
|
+
(process.env.DEV_WORKFLOW_DB_URL && process.env.DEV_WORKFLOW_DB_URL.startsWith("mysql") ? process.env.DEV_WORKFLOW_DB_URL : null);
|
|
80
|
+
|
|
81
|
+
await t.test("MysqlAdapter", { skip: !mysqlUrl }, async (t) => {
|
|
82
|
+
const adapter = new MysqlAdapter({ connectionUrl: mysqlUrl });
|
|
83
|
+
try {
|
|
84
|
+
// We use a random user ID to avoid clashing with verification script data
|
|
85
|
+
// although verification cleaned up mostly.
|
|
86
|
+
const testId = `test-mysql-${Date.now()}`;
|
|
87
|
+
const testProj = process.cwd();
|
|
88
|
+
await runGenericAdapterTests(t, adapter, testId, testProj);
|
|
89
|
+
} finally {
|
|
90
|
+
if (adapter.pool) await adapter.pool.end();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 3. PostgreSQL (Conditional)
|
|
95
|
+
const pgUrl = process.env.TEST_POSTGRES_URL ||
|
|
96
|
+
(process.env.DEV_WORKFLOW_DB_URL && process.env.DEV_WORKFLOW_DB_URL.startsWith("postgres") ? process.env.DEV_WORKFLOW_DB_URL : null);
|
|
97
|
+
|
|
98
|
+
await t.test("PostgresAdapter", { skip: !pgUrl }, async (t) => {
|
|
99
|
+
const adapter = new PostgresAdapter({ connectionUrl: pgUrl });
|
|
100
|
+
try {
|
|
101
|
+
const testId = `test-pg-${Date.now()}`;
|
|
102
|
+
const testProj = process.cwd();
|
|
103
|
+
await runGenericAdapterTests(t, adapter, testId, testProj);
|
|
104
|
+
} finally {
|
|
105
|
+
if (adapter.pool) await adapter.pool.end();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import { exec } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
test("Environment Configuration", async (t) => {
|
|
13
|
+
await t.test("loads variables from .env file", async () => {
|
|
14
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "env-test-"));
|
|
15
|
+
const originalCwd = process.cwd();
|
|
16
|
+
const indexJsPath = path.resolve(originalCwd, "index.js");
|
|
17
|
+
const indexUrl = pathToFileURL(indexJsPath).href;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Create a .env file
|
|
21
|
+
await fs.writeFile(path.join(tmpDir, ".env"), "TEST_ENV_VAR=loaded_successfully");
|
|
22
|
+
|
|
23
|
+
// Verify node can run and load it via index.js import
|
|
24
|
+
// We import index.js to trigger the 'dotenv/config' import.
|
|
25
|
+
// Since index.js starts a server if run directly, we just import it.
|
|
26
|
+
// However, we need to run a script in the NEW CWD.
|
|
27
|
+
|
|
28
|
+
const script = `
|
|
29
|
+
import '${indexUrl}';
|
|
30
|
+
console.log("VAR:" + process.env.TEST_ENV_VAR);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const scriptPath = path.join(tmpDir, "check_env.js");
|
|
35
|
+
await fs.writeFile(scriptPath, script);
|
|
36
|
+
|
|
37
|
+
// Execute in tmpDir
|
|
38
|
+
const { stdout } = await execAsync(`node check_env.js`, { cwd: tmpDir });
|
|
39
|
+
|
|
40
|
+
assert.ok(stdout.includes("VAR:loaded_successfully"), "Output should contain loaded variable");
|
|
41
|
+
|
|
42
|
+
} finally {
|
|
43
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|