molecule-core/mcp-server/src/index.ts
Hongming Wang 50b0a1859a refactor(mcp-server): DRY envelopes, typed apiCall, explicit re-exports
Second-pass cleanup after the monolith split. Addresses every issue
from the code-review pass.

Core additions in src/api.ts:
- toMcpResult(data) + toMcpText(text): single source of truth for the
  MCP text-content envelope (was ~87 duplicated literals)
- ApiError type + isApiError(v) guard: typed discriminated-union for
  the error-by-value pattern; replaces open-coded shape checks
- apiCall<T = unknown>: generic so callers can document expected
  response shape without unchecked "as" casts

Bulk cleanups across all 12 tools/*.ts:
- Every handler now returns toMcpResult(data) or toMcpText(text)
- Open-coded "typeof obj === 'object' && 'error' in obj" in
  remote_agents.ts replaced with isApiError(v)
- Extracted initialCanvasPosition() helper out of
  handleCreateWorkspace; explains why random seeding exists
- Added runtime/workspace_dir/workspace_access to create_workspace
  zod schema (previously accepted by handler but hidden from clients)

src/index.ts:
- Replaced "export * from" with explicit named re-exports so the
  public surface is auditable and future name collisions fail loudly

Tests:
- createServer() smoke test that records every srv.tool(...) call and
  asserts 87 registered tools unique by name. Catches future PRs that
  forget to wire a registerXxxTools(srv).

Docs:
- Fix broken relative links in sdk/python/molecule_agent/README.md
  (was ../../examples/ from inside sdk/python/, should be ../examples/)
- Update stale "61 tools" -> "87 tools" in CLAUDE.md + main() log

Verification:
- npm run build clean
- npx jest -> 97/97 passed (was 96; +1 smoke test)
- grep "content: [{ type: \"text\" as const" src/tools/ -> 0 matches
- No file over 216 lines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:26:17 -07:00

217 lines
5.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Molecule AI MCP Server
*
* Exposes Molecule AI platform operations as MCP tools so any AI coding agent
* (Claude Code, Cursor, Codex, OpenCode) can manage workspaces, agents,
* skills, and memory.
*
* Transport: stdio (for local CLI integration)
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { PLATFORM_URL, apiCall } from "./api.js";
import { registerWorkspaceTools } from "./tools/workspaces.js";
import { registerAgentTools } from "./tools/agents.js";
import { registerSecretTools } from "./tools/secrets.js";
import { registerFileTools } from "./tools/files.js";
import { registerMemoryTools } from "./tools/memory.js";
import { registerPluginTools } from "./tools/plugins.js";
import { registerChannelTools } from "./tools/channels.js";
import { registerDelegationTools } from "./tools/delegation.js";
import { registerScheduleTools } from "./tools/schedules.js";
import { registerApprovalTools } from "./tools/approvals.js";
import { registerDiscoveryTools } from "./tools/discovery.js";
import { registerRemoteAgentTools } from "./tools/remote_agents.js";
// Re-exports so existing importers (tests, SDK consumers) keep working.
// Explicit names (not `export *`) so tree-shakers and TS readers can see
// exactly which handlers are part of the public surface, and a missing
// export triggers a compile error instead of a silent undefined at import.
export { PLATFORM_URL, apiCall, isApiError, toMcpResult, toMcpText } from "./api.js";
export type { ApiError } from "./api.js";
export {
registerWorkspaceTools,
handleListWorkspaces,
handleCreateWorkspace,
handleGetWorkspace,
handleDeleteWorkspace,
handleRestartWorkspace,
handleUpdateWorkspace,
handlePauseWorkspace,
handleResumeWorkspace,
} from "./tools/workspaces.js";
export {
registerAgentTools,
handleChatWithAgent,
handleAssignAgent,
handleReplaceAgent,
handleRemoveAgent,
handleMoveAgent,
handleGetModel,
} from "./tools/agents.js";
export {
registerSecretTools,
handleSetSecret,
handleListSecrets,
handleDeleteSecret,
handleListGlobalSecrets,
handleSetGlobalSecret,
handleDeleteGlobalSecret,
} from "./tools/secrets.js";
export {
registerFileTools,
handleListFiles,
handleReadFile,
handleWriteFile,
handleDeleteFile,
handleReplaceAllFiles,
handleGetConfig,
handleUpdateConfig,
} from "./tools/files.js";
export {
registerMemoryTools,
handleCommitMemory,
handleSearchMemory,
handleDeleteMemory,
handleSessionSearch,
handleGetSharedContext,
handleSetKV,
handleGetKV,
handleListKV,
handleDeleteKV,
} from "./tools/memory.js";
export {
registerPluginTools,
handleListPluginRegistry,
handleListInstalledPlugins,
handleInstallPlugin,
handleUninstallPlugin,
handleListPluginSources,
handleListAvailablePlugins,
handleCheckPluginCompatibility,
} from "./tools/plugins.js";
export {
registerChannelTools,
handleListChannelAdapters,
handleListChannels,
handleAddChannel,
handleUpdateChannel,
handleRemoveChannel,
handleSendChannelMessage,
handleTestChannel,
handleDiscoverChannelChats,
} from "./tools/channels.js";
export {
registerDelegationTools,
handleAsyncDelegate,
handleCheckDelegations,
handleRecordDelegation,
handleUpdateDelegationStatus,
handleReportActivity,
handleListActivity,
handleNotifyUser,
handleListTraces,
} from "./tools/delegation.js";
export {
registerScheduleTools,
handleListSchedules,
handleCreateSchedule,
handleUpdateSchedule,
handleDeleteSchedule,
handleRunSchedule,
handleGetScheduleHistory,
} from "./tools/schedules.js";
export {
registerApprovalTools,
handleListPendingApprovals,
handleDecideApproval,
handleCreateApproval,
handleGetWorkspaceApprovals,
} from "./tools/approvals.js";
export {
registerDiscoveryTools,
handleListPeers,
handleDiscoverWorkspace,
handleCheckAccess,
handleListEvents,
handleListTemplates,
handleListOrgTemplates,
handleImportOrg,
handleImportTemplate,
handleExportBundle,
handleImportBundle,
handleGetViewport,
handleSetViewport,
handleExpandTeam,
handleCollapseTeam,
} from "./tools/discovery.js";
export {
registerRemoteAgentTools,
handleListRemoteAgents,
handleGetRemoteAgentState,
handleGetRemoteAgentSetupCommand,
handleCheckRemoteAgentFreshness,
} from "./tools/remote_agents.js";
export function createServer() {
const srv = new McpServer({
name: "molecule",
version: "1.0.0",
});
registerWorkspaceTools(srv);
registerAgentTools(srv);
registerSecretTools(srv);
registerFileTools(srv);
registerMemoryTools(srv);
registerPluginTools(srv);
registerChannelTools(srv);
registerDelegationTools(srv);
registerScheduleTools(srv);
registerApprovalTools(srv);
registerDiscoveryTools(srv);
registerRemoteAgentTools(srv);
return srv;
}
async function main() {
// Validate platform connectivity on startup
try {
const res = await fetch(`${PLATFORM_URL}/health`);
if (res.ok) {
console.error(`Molecule AI platform connected: ${PLATFORM_URL}`);
} else {
console.error(`WARNING: Molecule AI platform at ${PLATFORM_URL} returned ${res.status}. Tools may fail.`);
}
} catch {
console.error(`WARNING: Cannot reach Molecule AI platform at ${PLATFORM_URL}. Start it with: cd platform && go run ./cmd/server`);
}
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Molecule AI MCP server running on stdio (87 tools available)");
}
// Only auto-start when run directly (not when imported for testing).
// JEST_WORKER_ID is set automatically by Jest in every worker process.
if (!process.env.JEST_WORKER_ID) {
main().catch(console.error);
}