This commit is contained in:
Oleg Maslov
2026-09-02 10:14:22 +02:00
parent 0c3e2ead3b
commit b20b138fe4
771 changed files with 161561 additions and 9027 deletions

View File

@@ -33,6 +33,14 @@ import {
} from '@waggle/shared';
import type { McpRuntime, McpServerState } from '@waggle/agent';
import { scanForInjection, type RecordAuditInput } from '@waggle/core';
import {
MCP_SERVERS,
MarketplaceInstaller,
createMarketplaceMcpProvenance,
type InstallManifest,
type MarketplaceMcpProvenance,
type McpServerConfig,
} from '@waggle/marketplace';
import {
loadMcpConfig,
saveMcpServerEntry,
@@ -49,6 +57,13 @@ import { authHeaders, clampStr, clampStrArray } from './validate.js';
* under the instance's own 30s per-request timeout. */
const TEST_TIMEOUT_MS = 8_000;
const RESERVED_MARKETPLACE_MCP_NAMES = new Set(
[
...MCP_CATALOG.map((server) => server.id),
...MCP_SERVERS.flatMap((pkg) => pkg.install_manifest?.mcp_config?.name ?? []),
],
);
/** Clamp env to ≤64 pairs with bounded key/value lengths. Non-record shapes
* (and non-string values) pass through untouched so validateMcpEntry still
* rejects them with its precise error message. */
@@ -231,28 +246,53 @@ export async function mcpRoutes(fastify: FastifyInstance) {
}
// Resolve catalog id → marketplace package (mcp-registry seeds share ids)
const row = db.getRawDb().prepare(
"SELECT id FROM packages WHERE name = ? AND waggle_install_type = 'mcp'",
).get(mcpId) as { id: number } | undefined;
const row = db.getRawDb().prepare(`
SELECT p.id, p.name, p.version, p.install_manifest AS installManifest,
s.name AS sourceName, s.source_type AS sourceType,
s.is_custom AS sourceIsCustom
FROM packages p
INNER JOIN sources s ON s.id = p.source_id
WHERE p.name = ?
AND p.waggle_install_type = 'mcp'
AND s.name = 'mcp_registry'
AND s.source_type = 'registry'
AND s.is_custom = 0
ORDER BY p.id
LIMIT 1
`).get(mcpId) as {
id: number;
name: string;
version: string;
installManifest: string | InstallManifest;
sourceName: string;
sourceType: 'registry';
sourceIsCustom: number;
} | undefined;
if (!row) {
return reply.code(404).send({ error: `No marketplace MCP package named "${mcpId}"` });
}
// Resolve + validate the manifest BEFORE delegating: a package whose
// mcp_config fails the same validation the C4 boot loader applies would
// install "successfully" now and then be skipped at every reboot. Reject
// it up front (422) instead of half-installing.
const pkg = db.getPackage(row.id);
const manifest = pkg?.install_manifest as { mcp_config?: { name: string; command: string; args: string[]; env?: Record<string, string> } } | null;
const mcpConfig = manifest?.mcp_config;
if (!mcpConfig) {
return reply.code(422).send({ installed: false, error: 'Package manifest has no mcp_config' });
}
const manifestInvalid = validateMcpEntry(mcpConfig.name, { command: mcpConfig.command, args: mcpConfig.args, env: mcpConfig.env });
if (manifestInvalid) {
let expectedMcpProvenance: MarketplaceMcpProvenance;
try {
const manifest = (typeof row.installManifest === 'string'
? JSON.parse(row.installManifest)
: row.installManifest) as InstallManifest;
const mcpConfig = manifest.mcp_config;
if (!mcpConfig) throw new Error('Marketplace MCP snapshot has no configuration.');
expectedMcpProvenance = createMarketplaceMcpProvenance(
{
name: row.sourceName,
source_type: row.sourceType,
is_custom: Boolean(row.sourceIsCustom),
},
{ name: row.name, version: row.version },
mcpConfig,
);
} catch (err) {
return reply.code(422).send({
installed: false,
error: `Package mcp_config would not survive a restart (boot-loader validation): ${manifestInvalid}`,
success: false,
error: `Canonical marketplace MCP snapshot is invalid: ${(err as Error).message}`,
});
}
@@ -265,12 +305,17 @@ export async function mcpRoutes(fastify: FastifyInstance) {
settings: body.settings,
force: body.force,
forceInsecure: body.forceInsecure,
expectedInstallType: 'mcp',
expectedMcpProvenance,
},
});
const result = res.json() as {
success?: boolean;
blocked?: boolean;
scanResult?: { blocked?: boolean; overall_severity?: string };
mcpSourceConfig?: McpServerConfig;
mcpProvenance?: MarketplaceMcpProvenance;
errorCode?: 'PACKAGE_IDENTITY_CHANGED';
};
if (res.statusCode >= 400 || result.success === false) {
// A SecurityGate block (route-level 403 OR installer-level 422 with
@@ -285,28 +330,58 @@ export async function mcpRoutes(fastify: FastifyInstance) {
});
}
// The installer wrote the .mcp.json entry; apply the same env templating
// so the runtime registration matches what was persisted.
const env = mcpConfig.env ? { ...mcpConfig.env } : undefined;
if (body.settings && env) {
for (const [key, value] of Object.entries(body.settings)) {
for (const envKey of Object.keys(env)) {
if (env[envKey] === `\${${key}}` || env[envKey] === '') env[envKey] = value;
}
}
// Only the installer's exact validated source snapshot may reach an
// execution sink. The receipt is secret-free; settings are normalized by
// the same pure helper used for the installer's own .mcp.json.
if (!result.mcpSourceConfig || !result.mcpProvenance) {
return reply.code(500).send({
installed: false,
error: 'Marketplace installer returned no complete validated MCP receipt',
});
}
if (
row.name !== mcpId
|| !MarketplaceInstaller.mcpProvenanceMatches(result.mcpProvenance, expectedMcpProvenance)
|| result.mcpSourceConfig.name !== row.name
) {
return reply.code(409).send({
installed: false,
error: 'Marketplace MCP package changed during installation; retry from the refreshed catalog',
});
}
let configured: McpServerConfig;
try {
configured = MarketplaceInstaller.configureMcpServer(result.mcpSourceConfig, body.settings);
} catch (err) {
return reply.code(422).send({
installed: false,
error: `Marketplace MCP receipt validation failed: ${(err as Error).message}`,
});
}
const entry: PersistedMcpEntry = {
command: configured.command,
args: configured.args,
...(configured.env ? { env: configured.env } : {}),
provenance: result.mcpProvenance,
};
const entryInvalid = validateMcpEntry(configured.name, entry);
if (entryInvalid) {
return reply.code(422).send({
installed: false,
error: `Configured marketplace MCP would not survive a restart (boot-loader validation): ${entryInvalid}`,
});
}
const entry: PersistedMcpEntry = { command: mcpConfig.command, args: mcpConfig.args, ...(env ? { env } : {}) };
// Persist at the server's dataDir too — the installer writes to
// WAGGLE_DATA_DIR/~/.waggle, which may differ from a custom dataDir.
saveMcpServerEntry(dataDir(), mcpConfig.name, entry);
saveMcpServerEntry(dataDir(), configured.name, entry);
const runtime = getRuntime();
let status: McpServerState | 'unregistered' = 'unregistered';
let startError: string | undefined;
if (runtime) {
if (runtime.getServer(mcpConfig.name)) await runtime.removeServer(mcpConfig.name);
runtime.addServer({ name: mcpConfig.name, command: entry.command, args: entry.args, env: entry.env });
const instance = runtime.getServer(mcpConfig.name)!;
if (runtime.getServer(configured.name)) await runtime.removeServer(configured.name);
runtime.addServer({ name: configured.name, command: entry.command, args: entry.args, env: entry.env });
const instance = runtime.getServer(configured.name)!;
try {
await withTimeout(instance.start(), TEST_TIMEOUT_MS, 'MCP start');
} catch (err) {
@@ -327,7 +402,7 @@ export async function mcpRoutes(fastify: FastifyInstance) {
const scanSeverity = result.scanResult?.overall_severity;
const overrode = result.scanResult?.blocked === true;
recordMcpAudit({
capabilityName: mcpConfig.name,
capabilityName: configured.name,
source: 'marketplace',
riskLevel: scanSeverity === 'CRITICAL' ? 'critical'
: scanSeverity === 'HIGH' ? 'high'
@@ -344,8 +419,9 @@ export async function mcpRoutes(fastify: FastifyInstance) {
return {
installed: true,
mcpId,
server: mcpConfig.name,
server: configured.name,
status,
mcpProvenance: result.mcpProvenance,
...(startError ? { startError } : {}),
};
});
@@ -374,6 +450,11 @@ export async function mcpRoutes(fastify: FastifyInstance) {
? { workspaceId: typeof body.workspaceId === 'string' ? clampStr(body.workspaceId, 200) : body.workspaceId }
: {}),
};
if (RESERVED_MARKETPLACE_MCP_NAMES.has(name)) {
return reply.code(409).send({
error: `MCP server name "${name}" is reserved for verified marketplace installs`,
});
}
const invalid = validateMcpEntry(name, candidate);
if (invalid) return reply.code(400).send({ error: invalid });
@@ -522,36 +603,56 @@ export async function mcpRoutes(fastify: FastifyInstance) {
const { id } = request.params as { id: string };
const runtime = getRuntime();
const hadInstance = !!runtime?.getServer(id);
const persisted = loadMcpConfig(dataDir()).mcpServers[id];
const removedConfig = removeMcpServerEntry(dataDir(), id);
if (!hadInstance && !removedConfig) {
return reply.code(404).send({ error: `MCP server "${id}" is not installed` });
}
await runtime?.removeServer(id); // stops the process if running
const provenance = persisted?.provenance;
const verifiedMarketplaceOrigin = provenance?.kind === 'marketplace';
// Keep the marketplace's installed:true annotation honest (A4): if this
// server came from a marketplace package, retire that installation row
// too — otherwise /api/marketplace/search keeps claiming it's installed.
try {
const db = fastify.marketplace;
const pkgRow = db?.getRawDb().prepare(
"SELECT id FROM packages WHERE name = ? AND waggle_install_type = 'mcp'",
).get(id) as { id: number } | undefined;
if (pkgRow && db!.isInstalled(pkgRow.id)) {
db!.markUninstalled(pkgRow.id);
if (verifiedMarketplaceOrigin) {
try {
const db = fastify.marketplace;
const pkgRow = db?.getRawDb().prepare(`
SELECT p.id
FROM packages p
INNER JOIN sources s ON s.id = p.source_id
WHERE p.name = ?
AND p.version = ?
AND p.waggle_install_type = 'mcp'
AND s.name = ?
AND s.source_type = 'registry'
AND s.is_custom = 0
ORDER BY p.id
LIMIT 1
`).get(
provenance.packageName,
provenance.packageVersion,
provenance.sourceName,
) as { id: number } | undefined;
if (pkgRow && db!.isInstalled(pkgRow.id)) {
db!.markUninstalled(pkgRow.id);
}
} catch (err) {
fastify.log.warn({ err, id }, 'marketplace bookkeeping on MCP revoke failed (non-blocking)');
}
} catch (err) {
fastify.log.warn({ err, id }, 'marketplace bookkeeping on MCP revoke failed (non-blocking)');
}
recordMcpAudit({
capabilityName: id,
source: 'mcp',
source: verifiedMarketplaceOrigin ? 'marketplace' : 'mcp',
version: verifiedMarketplaceOrigin ? provenance.packageVersion : null,
riskLevel: 'low',
trustSource: 'local_user',
trustSource: verifiedMarketplaceOrigin ? 'third_party_verified' : 'local_user',
approvalClass: 'standard',
action: 'rejected',
action: 'uninstalled',
initiator: 'user',
detail: 'MCP server revoked — removed from runtime and persisted config',
detail: `MCP server revoked — removed from runtime and persisted config (${verifiedMarketplaceOrigin ? 'verified marketplace' : 'custom local'} origin)`,
});
return { ok: true, id, stoppedInstance: hadInstance, removedConfig };