Skip to content

Commit eb5ae35

Browse files
committed
merge: resolve upstream main conflicts against client-mapping reconciliation
Merges upstream mcpmux/mcp-mux main (through #215) into dev-rebased. Resolves conflicts between upstream's original #201/#202/#203/#205/#206 implementations and this fork's own reconciled port (Phases 1-4 of upstream-client-mapping-reconciliation.md) by keeping the fork's version wherever the two implement the same feature: - Migrations: kept 036-038 (fork numbering/shape); removed upstream's 020_inbound_client_api_keys.sql / 021_binding_type.sql / 022_inbound_client_locked_space.sql as superseded (Decision 6 — one reconciled migration set, not a straight append) - Resolver, repositories, domain, Tauri commands, Clients/Workspaces UI: kept the fork's Unbound-preserving, Tier 0/2 implementation over upstream's SpaceDefault-based one (Decisions 1/2) - navigation.ts / HomePage.tsx: kept the fork's nav (Decision 5 — no Apps->Clients / Workspaces->Mapping rename); HomePage.tsx stays deleted (already removed by the fork's earlier Dashboard-unification commit, unrelated to this merge) - Took upstream's unrelated CI hardening (apt cache pinning, Claude Code Actions workflow) and its new API-key e2e test as-is Autonomous decisions: - Fixed tests/rust/tests/database/migrations.rs upgrade-path regression test to reference migration 036/037/038 (not upstream's 020/021/022) and drop the partial indexes migration 037 creates before dropping the binding_type column, or the rollback step fails - Fixed 3 e2e specs asserting upstream's rejected nav rename ('Mapping') to assert the fork's real label ('Projects') instead Signed-off-by: crimsonsunset <jsangio1@gmail.com>
2 parents 30b2a17 + 87df4a2 commit eb5ae35

9 files changed

Lines changed: 192 additions & 14 deletions

File tree

.github/actions/install-linux-deps/action.yml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,30 @@ runs:
1313
using: composite
1414
steps:
1515
# Cache apt packages to avoid re-downloading on every run
16+
# The runner image ships an apt index from its build date, and
17+
# cache-apt-pkgs-action's own refresh (update_apt_lists_if_stale) is a
18+
# no-op outside nektos/act -- it guards on ACT=true. A stale index names
19+
# .debs that Ubuntu has since superseded and dropped from the pool, so the
20+
# install 404s; the action then caches the empty result instead of failing,
21+
# and every later run restores that as a hit and skips installing at all.
22+
- name: Refresh apt lists
23+
shell: bash
24+
run: sudo apt-get update
25+
26+
# Pinned to a SHA: `@latest` is a moving tag, so a broken upstream release
27+
# (e.g. v1.6.2) would otherwise roll straight into CI unreviewed.
1628
- name: Cache apt packages (base)
17-
uses: awalsh128/cache-apt-pkgs-action@latest
29+
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
1830
with:
1931
packages: build-essential pkg-config libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libsecret-1-dev libfuse2
20-
version: 1.1
32+
version: 1.2
2133

2234
- name: Cache apt packages (E2E)
2335
if: ${{ inputs.e2e == 'true' }}
24-
uses: awalsh128/cache-apt-pkgs-action@latest
36+
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
2537
with:
2638
packages: webkit2gtk-driver xvfb gnome-keyring gsettings-desktop-schemas dbus-x11 at-spi2-core libglib2.0-bin libwayland-server0 libwayland-client0
27-
version: 1.3
39+
version: 1.4
2840

2941
# Compile gsettings schemas (required after restore from cache)
3042
- name: Compile gsettings schemas

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ permissions:
1717
actions: read
1818
checks: write
1919
pull-requests: write
20+
id-token: write
2021

2122
env:
2223
CARGO_TERM_COLOR: always

.github/workflows/claude.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Claude Code
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request_review_comment:
7+
types: [created]
8+
issues:
9+
types: [opened, assigned]
10+
pull_request_review:
11+
types: [submitted]
12+
13+
jobs:
14+
claude:
15+
if: |
16+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
pull-requests: read
24+
issues: read
25+
id-token: write
26+
actions: read # Required for Claude to read CI results on PRs
27+
steps:
28+
- name: Checkout repository
29+
uses: actions/checkout@v4
30+
with:
31+
fetch-depth: 1
32+
33+
- name: Run Claude Code
34+
id: claude
35+
uses: anthropics/claude-code-action@v1
36+
with:
37+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38+
39+
# This is an optional setting that allows Claude to read CI results on PRs
40+
additional_permissions: |
41+
actions: read
42+
43+
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
44+
# prompt: 'Update the pull request description to include a summary of changes.'
45+
46+
# Optional: Add claude_args to customize behavior and configuration
47+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
48+
# or https://code.claude.com/docs/en/cli-reference for available options
49+
# claude_args: '--allowed-tools Bash(gh pr *)'
50+

tests/e2e/specs/app.wdio.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('McpMux Application', () => {
5656
await safeClick(clientsButton);
5757
await browser.pause(1500);
5858
const pageSource = await browser.getPageSource();
59-
expect(pageSource.includes('Apps') || pageSource.includes('clients-page')).toBe(true);
59+
expect(pageSource.includes('Clients') || pageSource.includes('clients-page')).toBe(true);
6060
});
6161

6262
it('should navigate to FeatureSets page', async () => {

tests/e2e/specs/clients.wdio.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ describe('Connections - Page shell', () => {
2222
const pageSource = await browser.getPageSource();
2323

2424
// Heading has been renamed.
25-
expect(pageSource.includes('Apps')).toBe(true);
25+
expect(pageSource.includes('Clients')).toBe(true);
2626

27-
// The page routes users to Workspaces for any routing questions.
28-
expect(pageSource.includes('Workspaces')).toBe(true);
27+
// The page routes users to the Projects tab for any routing questions.
28+
expect(pageSource.includes('Projects')).toBe(true);
2929
});
3030

3131
it('TC-CL-002: Open side panel and verify legacy routing controls are gone', async () => {
@@ -57,4 +57,32 @@ describe('Connections - Page shell', () => {
5757
expect(pageSource.includes("Let's hook up your first IDE")).toBe(true);
5858
}
5959
});
60+
61+
it('TC-CL-003: Register an API-key client and reveal the key once', async () => {
62+
// The desktop app auto-starts the gateway, so register_api_key_client can
63+
// mint a key. Open the Apps tab, register a client, and confirm the
64+
// generated mcpk_ key is shown exactly once.
65+
const connectionsBtn = await byTestId('nav-clients');
66+
await connectionsBtn.click();
67+
await browser.pause(1000);
68+
69+
const registerBtn = await byTestId('register-api-key-client-btn');
70+
await registerBtn.click();
71+
await browser.pause(800);
72+
73+
const nameInput = await byTestId('register-api-key-name');
74+
await nameInput.setValue('e2e-headless-bot');
75+
76+
const generateBtn = await byTestId('register-api-key-generate');
77+
await generateBtn.click();
78+
await browser.pause(1500);
79+
80+
await browser.saveScreenshot('./tests/e2e/screenshots/cl-03-api-key-created.png');
81+
82+
const keyEl = await byTestId('register-api-key-value');
83+
await expect(keyEl).toBeDisplayed();
84+
const keyText = await keyEl.getText();
85+
// Shown once, prefixed mcpk_ (never the stored hash).
86+
expect(keyText.startsWith('mcpk_')).toBe(true);
87+
});
6088
});

tests/e2e/specs/navigation.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ test.describe('Navigation', () => {
3030
await page.locator('nav button:has-text("Search")').click({ force: true });
3131
await expect(page.locator('h1:has-text("Discover Servers")')).toBeVisible();
3232

33-
// Spaces (use last() to avoid space switcher)
34-
await page.locator('nav button:has-text("Spaces")').last().click({ force: true });
35-
await expect(page.locator('h1:has-text("Workspaces")')).toBeVisible();
33+
// Projects (the folder → bundle mapping tab)
34+
await page.locator('nav button:has-text("Projects")').last().click({ force: true });
35+
await expect(page.locator('h1:has-text("Projects")')).toBeVisible();
3636

3737
// FeatureSets
3838
await page.locator('nav button:has-text("Bundles")').click({ force: true });

tests/e2e/specs/user-flows.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ test.describe('Complete User Flows', () => {
4444
await page.locator('nav button:has-text("Search")').click();
4545
await expect(page.locator('h1:has-text("Discover Servers")')).toBeVisible();
4646

47-
// Spaces (use last() to avoid space switcher)
48-
await page.locator('nav button:has-text("Spaces")').last().click();
49-
await expect(page.locator('h1:has-text("Workspaces")')).toBeVisible();
47+
// Projects (the folder → bundle mapping tab)
48+
await page.locator('nav button:has-text("Projects")').last().click();
49+
await expect(page.locator('h1:has-text("Projects")')).toBeVisible();
5050

5151
// FeatureSets
5252
await page.locator('nav button:has-text("Bundles")').click();

tests/rust/tests/database/migrations.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,89 @@ fn test_018_rewrites_stale_starter_description_only() {
151151
"operator-customized copy must be preserved"
152152
);
153153
}
154+
155+
// ---------------------------------------------------------------------------
156+
// Upgrade path — applying NEW migrations to an EXISTING (older) on-disk DB.
157+
//
158+
// Every other test here uses a FRESH in-memory DB, where all migrations run at
159+
// once — so they never catch a migration that fails to apply when a real user
160+
// opens a database created by a previous release. These two do.
161+
// ---------------------------------------------------------------------------
162+
163+
fn table_exists(db: &Database, name: &str) -> bool {
164+
db.connection()
165+
.query_row(
166+
"SELECT COUNT(*) > 0 FROM sqlite_master WHERE type='table' AND name=?1",
167+
[name],
168+
|r| r.get::<_, bool>(0),
169+
)
170+
.unwrap_or(false)
171+
}
172+
173+
fn column_exists(db: &Database, table: &str, column: &str) -> bool {
174+
let sql = format!("SELECT COUNT(*) > 0 FROM pragma_table_info('{table}') WHERE name=?1");
175+
db.connection()
176+
.query_row(&sql, [column], |r| r.get::<_, bool>(0))
177+
.unwrap_or(false)
178+
}
179+
180+
#[test]
181+
fn test_new_schema_objects_exist_after_migration() {
182+
// A fresh migrate must produce every object the API-key + mapping features
183+
// depend on — a regression guard against a migration being dropped or broken.
184+
let db = Database::open_in_memory().expect("open");
185+
assert!(
186+
table_exists(&db, "inbound_client_api_keys"),
187+
"migration 036 must create inbound_client_api_keys"
188+
);
189+
assert!(
190+
column_exists(&db, "workspace_bindings", "binding_type"),
191+
"migration 037 must add workspace_bindings.binding_type"
192+
);
193+
assert!(
194+
column_exists(&db, "inbound_clients", "locked_space_id"),
195+
"migration 038 must add inbound_clients.locked_space_id"
196+
);
197+
}
198+
199+
#[test]
200+
fn test_pending_migrations_apply_to_an_existing_older_database() {
201+
// Reproduce the real upgrade that surfaced "no such table:
202+
// inbound_client_api_keys" in the field: a DB created before 036/037/038
203+
// existed, reopened by a newer build. The pending migrations MUST apply.
204+
let dir = tempfile::tempdir().expect("tempdir");
205+
let path = dir.path().join("mcpmux.db");
206+
207+
// 1. Fully migrate, then roll the schema back to a pre-036 state.
208+
{
209+
let db = Database::open(&path).expect("open");
210+
db.connection()
211+
.execute_batch(
212+
"DELETE FROM schema_migrations WHERE version >= 36;
213+
DROP TABLE IF EXISTS inbound_client_api_keys;
214+
DROP INDEX IF EXISTS idx_wb_root_global;
215+
DROP INDEX IF EXISTS idx_wb_root_machine;
216+
DROP INDEX IF EXISTS idx_wb_root_scoped;
217+
DROP INDEX IF EXISTS idx_wb_id_global;
218+
DROP INDEX IF EXISTS idx_wb_id_machine;
219+
DROP INDEX IF EXISTS idx_workspace_bindings_binding_type;
220+
DROP INDEX IF EXISTS idx_inbound_clients_locked_space_id;
221+
ALTER TABLE workspace_bindings DROP COLUMN binding_type;
222+
ALTER TABLE inbound_clients DROP COLUMN locked_space_id;",
223+
)
224+
.expect("roll schema back to pre-036");
225+
assert!(
226+
!table_exists(&db, "inbound_client_api_keys"),
227+
"precondition: the rolled-back DB is missing the table"
228+
);
229+
}
230+
231+
// 2. Reopen — run_migrations() must re-apply 036/037/038.
232+
let db = Database::open(&path).expect("reopen older DB");
233+
assert!(
234+
table_exists(&db, "inbound_client_api_keys"),
235+
"reopening an older DB must re-create inbound_client_api_keys"
236+
);
237+
assert!(column_exists(&db, "workspace_bindings", "binding_type"));
238+
assert!(column_exists(&db, "inbound_clients", "locked_space_id"));
239+
}

tests/ts/components/WorkspaceSetupWizard.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ describe('WorkspaceSetupWizard', () => {
8181
workspace_root: '/proj/app',
8282
space_id: 's1',
8383
feature_set_ids: ['fs_starter'],
84+
binding_type: 'path',
8485
});
8586
// The parent navigates to the new mapping's inspector (effective features);
8687
// the wizard itself does not close.

0 commit comments

Comments
 (0)