Are you the author? Sign in to claim
Archon AI Node companion - multi-engine chat adapters, MCP routing, OAuth broker
Node.js + TypeScript server that runs Synapse AI agent graphs. Salesforce stores config and audit; this server does all AI inference and MCP tool execution so Apex stays inside governor limits.
Salesforce org Synapse AI server (this repo)
┌───────────────────┐ JWT-signed HTTPS ┌─────────────────────────────┐
│ AgentBuilder UI │ ────────────────────▶ │ POST /agent/execute │
│ AgentRunner.cls │ Named Credential │ ├ verify JWT │
│ Trigger handlers │ "Agent_Platform" │ ├ load AgentDefinition │
│ │ ◀── Platform Event ─── │ ├ walk graph │
│ AgentExecution__c │ AgentExecutionResult │ ├ exec nodes │
└───────────────────┘ │ │ ├ claude (ai-models) │
│ │ ├ get/update record │
│ │ └ if/else, loop ... │
│ └ publish result event │
└─────────────────────────────┘
tsx need modern Node)sk-ant-...)openssl for generating the JWT secret and SF Connected App certcd server
npm install
cp .env.example .env
Generate a JWT secret:
openssl rand -hex 32
# paste the output as JWT_SECRET in .env
Set ANTHROPIC_API_KEY to your key from https://console.anthropic.com/.
The Salesforce values (SF_CLIENT_ID, SF_USERNAME, SF_PRIVATE_KEY_PATH) come from step 4.
Dev mode (auto-reload):
npm run dev
Production:
npm run build
npm start
You should see synapse_ai_server_started in the log and GET /health returning {"status":"ok"}.
ngrok http 3000
# → https://abc123.ngrok-free.app
In Setup → Named Credentials → Agent Platform:
Salesforce's Named Credential alone can send an unsigned bearer header. To get HMAC-signed JWTs that this server can verify, customers usually:
Option A — Use an External Credential (recommended for production)
JwtSecret = the same hex string in JWT_SECRETAgent_Platform Named CredentialHttpCalloutAction Apex class (or use Flow HTTP Callouts) that mints a JWT in Apex using Crypto.generateMac('HmacSHA256', ...) and sets Authorization: Bearer <jwt> on the requestOption B — Quick dev shortcut
In AgentBuilderController.executeAgent and AgentRunner.callExternalEngine, sign the JWT inline before the callout:
String jwt = mintHs256Jwt(new Map<String,Object>{
'orgId' => UserInfo.getOrganizationId(),
'userId' => UserInfo.getUserId(),
'agentApiName' => agentApiName,
'iat' => DateTime.now().getTime()/1000,
'exp' => (DateTime.now().getTime()/1000) + 300 // 5 min
}, jwtSecretFromCustomMetadata());
req.setHeader('Authorization', 'Bearer ' + jwt);
Where mintHs256Jwt does the base64url(header) + base64url(payload) + HMAC-SHA256 signature. We can add this helper in a follow-up commit.
The server logs in to Salesforce as itself (a System Integration user) to load agent definitions and write audit logs back. Use JWT Bearer Token Flow:
openssl req -x509 -nodes -newkey rsa:2048 -keyout keys/server.key -out keys/server.crt -days 365 -subj "/CN=SynapseAIServer"server.crtapi, refresh_token, offline_accessSF_CLIENT_IDSF_USERNAME to that user's usernameSF_PRIVATE_KEY_PATH=./keys/server.keysf project deploy start --target-org <your-org>sf org assign permset --name AgentBuilderUserlead_qualifier, set Status to Activeclaude_call_complete with cache_creation on the first call, then cache_read > 0 on subsequent calls (proving the knowledge-base prompt cache is working)src/
├── index.ts Express entry, mounts routers, error handler
├── config.ts Typed env config
├── logger.ts pino logger
├── types.ts Shared types (AgentDefinition, NodeResult, ...)
├── auth/jwt.ts JWT verify middleware
├── routes/
│ ├── agent.routes.ts POST /agent/execute, GET /agent/status/:id
│ └── health.routes.ts GET /health
├── orchestrator/
│ ├── engine.ts runAgent() — BFS walks the graph
│ ├── context.ts ExecutionContext + {!var} interpolation
│ └── graph.ts Builds adjacency map from CanvasJson__c
├── nodes/
│ ├── registry.ts subType → executor lookup
│ ├── trigger.ts record / schedule / webhook / platform_event
│ ├── ai.ts claude (real), gpt4 / einstein / sentiment (stubs)
│ ├── action.ts get/update/create/query record, create_task, post_chatter
│ ├── channel.ts outlook / gmail / sendgrid / twilio / slack / teams (stubs)
│ ├── logic.ts if_else (real), loop / wait / approval
│ └── end.ts
├── mcp/
│ ├── registry.ts MCP server name lookup
│ └── servers/
│ ├── ai-models.ts Claude integration (Opus 4.7 + adaptive thinking + prompt caching)
│ └── salesforce-crm.ts jsforce-backed CRUD + SOQL
└── salesforce/
├── client.ts jsforce JWT-bearer login, loadAgentDefinition()
└── callback.ts Publishes AgentExecutionResult__e events
src/mcp/servers/src/nodes/<category>.ts and call register('your_subtype', execFn)src/orchestrator/engine.ts (side-effect import — runs the register() call)agentPropertiesPanel.js so the canvas UI shows config inputsNODE_PALETTE in agentCanvas.js so the node appears in the palettesrc/mcp/servers/<name>.ts and export typed functions (callX, queryY, ...)<name> to the MCP_SERVERS const in src/mcp/registry.tsWhen the official MCP wire protocol stabilizes, swap the direct imports for an MCP client that dispatches by node.mcpServer + node.mcpTool — the registry is already shaped for this.
npm run build && npm start as the start command.serverless-http — note that JWT-bearer SF logins should be cached in a warm container or pulled from Secrets Manager.node:20-alpine) is the standard path.system with cache_controleffort: "high" on Opus 4.7 gives the best quality; drop to medium if you need lower per-call costclaude-opus-4-7 → claude-haiku-4-5 per node — the UI exposes this in the properties panelRun Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
Google's universal MCP server supporting PostgreSQL, MySQL, MongoDB, Redis, and 10+ databases
Official GitHub integration for repos, issues, PRs, and CI/CD workflows