Atlas / MCP servers / ttoss / SOAT — open-source infrastructure for production-ready AI ag

SOAT — open-source infrastructure for production-ready AI agBLOCK

mcp/ttoss/soat

SOAT — open-source infrastructure for production-ready AI agents.

Verdict
BLOCK
Grade
F
Trust score
48 /100
Exposed tools
53 39r · 13w · 1d
Transport
—
License
Apache-2.0
Stars
4
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

[](https://opensource.org/licenses/Apache-2.0) [](https://safeskill.dev/scan/ttoss-soat) [](https://hub.docker.com/r/ttoss/soat) [](https://hub.docker.com/r/ttoss/soat)

SOAT is open-source infrastructure for building AI applications. One self-hostable Node.js server gives you IAM, file and document storage with vector search, multimodal ingestion, conversational memory, agent orchestration, DAG-based multi-agent workflows, retrieval-augmented generation, guardrails and human approvals, usage metering and quotas, versioned agents with canary rollout, declarative stack deployment, a built-in web console, and a full Model Context Protocol server with first-party OAuth — backed by PostgreSQL.

You bring the product. SOAT handles the infrastructure layer.

Why SOAT?

Shipping AI applications means rebuilding the same infrastructure on every project: users, API keys, encrypted secrets, file storage, embeddings, conversation history, agent tool calling, traces, observability. SOAT solves all of it once and exposes it through four equivalent surfaces — REST, MCP, CLI, and TypeScript SDK — plus a built-in web console on top of them, so the same operation runs the same way whether you call it from a backend, Claude Desktop, a CI script, the bundled UI, or your own frontend.

SOAT organizes that surface around the four layers of an agent system: the harness (what an agent can reach and what it is forbidden), t

Read from source at commit 9539368356b6OBSERVED · 2026-10-12
Advertisement
02

Connect

Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.

claude-code
claude mcp add website --env AI_PROVIDER_ALLOW_AMBIENT_CREDENTIALS=${AI_PROVIDER_ALLOW_AMBIENT_CREDENTIALS} --env CHANNEL_HANDLER_SECRET=${CHANNEL_HANDLER_SECRET} --env DATABASE_PASSWORD=${DATABASE_PASSWORD} --env EMBEDDING_API_KEY=${EMBEDDING_API_KEY} -- npx -y @soat/[email protected]
claude-desktop
{
  "mcpServers": {
    "website": {
      "command": "npx",
      "args": [
        "-y",
        "@soat/[email protected]"
      ],
      "env": {
        "AI_PROVIDER_ALLOW_AMBIENT_CREDENTIALS": "${AI_PROVIDER_ALLOW_AMBIENT_CREDENTIALS}",
        "CHANNEL_HANDLER_SECRET": "${CHANNEL_HANDLER_SECRET}",
        "DATABASE_PASSWORD": "${DATABASE_PASSWORD}",
        "EMBEDDING_API_KEY": "${EMBEDDING_API_KEY}"
      }
    }
  }
}
03

Exposed tools (53)

39 read · 13 write · 1 destructive. Blast radius: 1 tool can delete or overwrite — an agent that can be talked into calling a tool can be talked into calling this one.

ToolRiskDescription
2026-09-11-memory-tags-to-jsonbreadMemory tags from text[] to key-value jsonb, aligning them with every other tagged resource.
2026-09-16-memories-rename-and-provenancewritememories -> memory_stores and memory_entries -> memories, with source_conversation_id and source_generation_id collapsed into source_id.
2026-09-16-memory-assertions-and-shared-contentwritememories.content/embedding move to a shared memory_contents row, memory_assertions records every write, and generations.conversation_id plus the memory_stores threshold pair are added.
2026-09-16-memory-rules-from-agent-extractionreadmemory_rules is added and owned by the memory store, memory_assertions.rule_id becomes a foreign key into it, and every agent with knowledge_config.extraction becomes one rule before the field is stripped.
2026-09-17-memory-assertions-declaredreadmemory_assertions.declared records whether a supersede was declared by the caller or chosen by the thresholds.
2026-09-18-activity-entry-generation-idreadactivity_entries.generation_id is promoted from the detail blob to an indexed column so the feed can be filtered by generation.
2026-09-18-orchestration-run-idempotency-keywriteorchestration_runs.idempotency_key, unique per project, so a retried run start cannot start a second run.
2026-09-20-orchestration-node-execution-dispatchesreadorchestration_node_executions.dispatches counts the dispatches one attempt issued, so a redelivered node execution is visible on the run.
2026-09-20-trigger-tool-contextwritetriggers.tool_context stores the caller context a firing forwards, so a scheduled run can reach a tool that authorizes per call.
2026-09-21-document-metadata-jsonbreaddocuments.metadata from serialized text to jsonb, so a filter can reach the fields inside a caller bag.
2026-09-21-document-versionswritedocuments.version counts content versions, so a write can be archived and restored the way an agent config can.
2026-09-21-durable-event-firingsreadtrigger_firings gains idempotency_key, causation_chain, attempts and lease_expires_at, so an event firing survives the process that matched it.
2026-09-21-memory-versionwritememories.version counts concurrent writes, so PUT /memories/{id} can state a write precondition.
2026-09-22-file-metadata-jsonbreadfiles.metadata from serialized text to jsonb, so the column and the wire field carry one shape.
2026-09-22-tag-bag-gin-indexesreadA GIN index with jsonb_path_ops over every tags column, so containment is a lookup rather than a scan.
2026-09-24-api-key-sha256readapi_keys.key_hash_sha256, the SHA-256 a key is verified by, and api_keys.key_hash nullable, since a SHA-256 key carries no bcrypt hash.
2026-09-24-foreign-key-nullabilityreadagents.ai_provider_id, chats.ai_provider_id, api_keys.project_id and conversation_messages.actor_id nullable, as their models declare.
2026-09-24-generation-idempotency-keyreadgenerations.idempotency_key, unique per project, and the digest of the request it names, so a retried generation cannot run the agent twice.
2026-09-24-orchestration-output-mappingwriteorchestrations.output_mapping declares the shape of a run output, independent of terminal node ids.
2026-09-25-usage-event-generation-public-idreadusage_events.generation_public_id, a durable copy of the generation public id that totals.distinct.generations counts over.
2026-09-26-usage-event-document-and-memory-storereadusage_events.document_id and usage_events.memory_store_id, what an embedding event embedded.
2026-09-26-usage-event-durable-public-idsreadusage_events gains a public id beside each attribution FK, which totals.distinct counts so a deleted entity stays counted.
2026-09-27-decider-tool-backendreaddeciders.tool_id, the tool backend, and deciders.agent_id nullable.
2026-09-29-eval-group-bywriteevals.group_by names the item metadata key a run aggregates scores by.
2026-09-29-eval-run-decider-versionswriteeval_runs.decider_versions pins the decider version each decider scorer grades under.
2026-09-30-agent-records-outlive-agentreadgenerations, traces and sessions keep the agent public id and survive the agent being deleted.
2026-09-30-ingestion-rules-outlive-converterreadingestion rules keep their converter public id and survive the converter being deleted.
2026-09-30-usage-event-publisher-projectreadusage_events.publisher_project_id, the project that owns a resource called through a share.
2026-10-01-generation-input-modalitiesreadgenerations.input_modalities, the part types a turn
2026-10-01-price-book-resourcereadprice_books.resource and quantity, a row pricing one tool by what a call consumed.
2026-10-01-share-capreadshares.cap, the calls each acceptance may make per window.
2026-10-07-decisions-openai-shapereadDecider questions and decision answers in the OpenAI Decisions API shape; decisions.questions for inline decisions.
AgentsreadManage AI agents
FilesreadManage files
Renamedreadnow described
areadb
channelreadA messaging channel.
create-share-toolwriteReads text off an image
delete_everythingdestructiveRemoves it all
env-filereadPath to .env file for parameter variable substitution
fmlife-hook-v2readUpdated description
fmlife-storereadoriginal
get-doc-pagereadFetches the full content of a SOAT documentation page by URL. Use get-docs first to obtain the list of valid page URLs.
get-docsreadReturns the SOAT documentation index in llms.txt format. The response lists all available documentation pages with their URLs. Use get-doc-page with a URL from this index to read a specific page.
get_order_statuswriteLooks up an order in the store database.
get_weatherreadReturns the weather for a city.
inline-weather-toolreadGets the weather
keptwriteset out of band
lookupOrderwriteLook an order up
my-http-tool-updatedreadUpdated description
renamed-toolreadUpdated desc
template-pathreadPath to template file (JSON or YAML). Alias: --template-file
test_channelreadA test channel.
04

Trust audit

BLOCKgrade F · trust 48/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeFAIL
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfaceWARN
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (3 observation(s))
Network
declared (7 observation(s))
Shell
declared (2 observation(s))
Dependencies
not all pinned
Secrets in source
found

Findings (25)

HIGHCode injection · code.deserialize · CWE-78, CWE-94, CWE-95
packages/cli/scripts/generate.ts:54
const doc: unknown = yaml.load(
Why it matters. deserialises untrusted bytes into live objects
Fix. use json or yaml.safe_load
MEDIUMInsecure crypto · crypto.weak_random · CWE-327, CWE-338
packages/server/src/lib/releaseAssignment.ts:150
key === null ? Math.floor(Math.random() * BUCKETS) : bucketForKey(key);
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
packages/server/tests/unit/tests/lib/agentModel.test.ts:295
).toEqual({ apiKey: 'AIzaSyExpressModeKey' });
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
packages/server/tests/unit/tests/rest/webhookDelivery.test.ts:581
{ secret: 'not-valid-ciphertext' },
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
packages/server/tests/unit/tests/lib/agentModel.test.ts:24
private_key: '-----BEGIN PRIVATE KEY-----\nnot-a-real-key\n-----END',
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
packages/server/tests/unit/tests/lib/agentModel.test.ts:269
private_key: '-----BEGIN PRIVATE KEY-----\nnot-a-real-key\n-----END',
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
packages/server/tests/unit/tests/lib/agentModel.test.ts:336
private_key: '-----BEGIN PRIVATE KEY-----\nnot-a-real-key\n-----END',
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
packages/server/tests/unit/tests/lib/aiProviderModels.test.ts:234
private_key: '-----BEGIN PRIVATE KEY-----\nnot-a-real-key\n-----END',
MEDIUMHard-coded secrets · secret.private_key · CWE-798, CWE-321
packages/server/tests/unit/tests/lib/aiProviderModels.test.ts:768
'-----BEGIN PRIVATE KEY-----\nx\n-----END PRIVATE KEY-----',
MEDIUMFilesystem / path · mcp.destructive_tools · CWE-22, CWE-59
delete_everything
Why it matters. 1 tool(s) can delete or overwrite
Fix. prefer a read-only mode or scoped tokens; the page states the blast radius
LOWInventory / provenance · inv.hidden_file · CWE-1104
.commitlintrc.js
.commitlintrc.js
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.lintstagedrc.js
.lintstagedrc.js
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.ncurc.js
.ncurc.js
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.prettierignore
.prettierignore
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
.prettierrc.js
.prettierrc.js
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWCode injection · code.deserialize · CWE-78, CWE-94, CWE-95
packages/cli/tests/unit/tests/flagTypeFidelity.test.ts:32
const spec = yaml.load(
Why it matters. deserialises untrusted bytes into live objects
Fix. use json or yaml.safe_load
LOWCode injection · code.deserialize · CWE-78, CWE-94, CWE-95
packages/cli/tests/unit/tests/flagTypeFidelity.test.ts:81
const spec = yaml.load(
Why it matters. deserialises untrusted bytes into live objects
Fix. use json or yaml.safe_load
LOWCode injection · code.deserialize · CWE-78, CWE-94, CWE-95
packages/website/scripts/seoMetadata.test.ts:135
return match ? ((yaml.load(match[1]) ?? {}) as Record<string, unknown>) : {};
Why it matters. deserialises untrusted bytes into live objects
Fix. use json or yaml.safe_load
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
packages/website/scripts/viewerRequest.test.ts:54
const noMarkdownUris = new Function(
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
packages/website/scripts/viewerRequest.test.ts:59
const handler = new Function('appendIndexHtml', `${source}\nreturn handler;`)(
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWInsecure crypto · crypto.weak_hash · CWE-327, CWE-338
packages/server/tests/unit/fixtures/fakeSqs.ts:49
return crypto.createHash('md5').update(value, 'utf8').digest('hex');
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
packages/cli/scripts/generate.ts:20
const SPECS_DIR = path.resolve(__dirname, '../../server/src/rest/openapi/v1');
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
packages/cli/tests/unit/testClient.ts:21
return import('../../src/index');
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
packages/cli/tests/unit/tests/auth.test.ts:3
import { resolveClient } from '../../../src/config';
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
packages/cli/tests/unit/tests/flagTypeFidelity.test.ts:6
import { routes } from '../../../src/generated/routes';

Gates applied: no_behavioural_pass.

Audited 2026-10-12 · audit v0.4.1 · source sha 9539368356b6full audit observations/trust-audit/mcp-server/ttoss__soat.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-129539368356b6BLOCKF48first audit
06

Questions

What is the @soat/cli MCP server?

SOAT — open-source infrastructure for production-ready AI agents.

What tools does @soat/cli expose?

53 in total: 39 read-only, 13 that write, and 1 that can delete or overwrite (delete_everything). Every one is listed on this page with its risk.

Is @soat/cli safe to connect to an agent?

No — not without reading the findings first. The audit graded it F (48/100) and found 1 critical or high issue in the source. Each one is listed on this page with the file and line it is on. Separately from the audit: 1 of its tools can destroy data, so scope the token you give it to what you actually need.

What credentials does @soat/cli need?

It reads AI_PROVIDER_ALLOW_AMBIENT_CREDENTIALS, CHANNEL_HANDLER_SECRET, DATABASE_PASSWORD, EMBEDDING_API_KEY, EMBEDDING_INPUT_1M_TOKEN_PRICE_USD, FILES_S3_KEY_PREFIX, FORMATION_HANDLER_SECRET, GOOGLE_APPLICATION_CREDENTIALS, JWT_SECRET, MAX_COMPLETION_TOKENS, OPENAI_API_KEY and SECRETS_ENCRYPTION_KEY from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.

How current is this page?

The grade is for one exact copy of the source (9539368356b6), read on 2026-10-12. The repository is watched and re-audited when it changes.

Advertisement