nocturnusai-connect — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited nocturnusai-connect (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
NocturnusAI is a logic server for Agentic AI that provides deterministic multi-step reasoning, truth maintenance, and agent memory lifecycle management via HTTP API, MCP protocol, and client SDKs (Python, TypeScript). Agents use NocturnusAI as their semantic memory and reasoning backend — storing facts, defining rules, running inference, and managing context windows with temporal awareness and salience scoring. This skill covers all connection paths, authentication modes, and tenant setup.
The NocturnusAI server must be running before attempting any connection.
Start the server (choose one):
# Option 1: Gradle (development)
./gradlew :nocturnusai-server:run
# Option 2: Docker
docker-compose up --build
# Option 3: Local dev script
./run_local_dev.shDefault port: 9300 Default host: 0.0.0.0
Verify the server is running:
curl http://localhost:9300/healthNocturnusAI exposes MCP via POST /mcp (JSON-RPC 2.0) and GET /mcp/sse (SSE streaming transport). Because Claude Desktop cannot inject custom headers like X-Tenant-ID natively, use the mcp-remote stdio bridge.
.mcp.json (project-level){
"mcpServers": {
"nocturnusai": {
"command": "npx",
"args": [
"mcp-remote",
"--header", "X-Tenant-ID:${NOCTURNUSAI_TENANT}",
"--header", "X-Database:${NOCTURNUSAI_DATABASE}",
"--header", "X-API-Key:${NOCTURNUSAI_API_KEY}",
"${NOCTURNUSAI_URL}/mcp/sse"
],
"env": {
"NOCTURNUSAI_URL": "http://localhost:9300",
"NOCTURNUSAI_API_KEY": "",
"NOCTURNUSAI_DATABASE": "default",
"NOCTURNUSAI_TENANT": "default"
}
}
}
}claude_desktop_config.json{
"mcpServers": {
"nocturnusai": {
"command": "npx",
"args": [
"mcp-remote",
"--header", "X-Tenant-ID:default",
"--header", "X-Database:default",
"http://localhost:9300/mcp/sse"
]
}
}
}| Variable | Default | Purpose |
|---|---|---|
NOCTURNUSAI_URL | http://localhost:9300 | Server base URL |
NOCTURNUSAI_API_KEY | _(empty)_ | API key for authentication |
NOCTURNUSAI_DATABASE | default | Target database name |
NOCTURNUSAI_TENANT | default | Tenant ID for data isolation |
Once connected, these MCP tools are available: tell, ask, teach, forget, inspect, context, aggregate, bulk_assert, retract_pattern, fork_scope, merge_scope, list_scopes, delete_scope.
Note: MCP routes default X-Tenant-ID to "default" if not provided. REST routes require it explicitly.
# Tell a fact via MCP
curl -X POST http://localhost:9300/mcp \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: default" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "tell",
"arguments": {
"predicate": "likes",
"args": ["alice", "bob"]
}
}
}'# Ask a query via MCP
curl -X POST http://localhost:9300/mcp \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: default" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "ask",
"arguments": {
"predicate": "likes",
"args": ["alice", "?who"]
}
}
}'# Tell a fact
curl -X POST http://localhost:9300/tell \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: my-tenant" \
-d '{"predicate": "likes", "args": ["alice", "bob"]}'
# Ask a query
curl -X POST http://localhost:9300/ask \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: my-tenant" \
-d '{"predicate": "likes", "args": ["alice", "?who"]}'
# Teach a rule
curl -X POST http://localhost:9300/teach \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: my-tenant" \
-d '{
"head": {"predicate": "mortal", "args": ["?x"]},
"body": [{"predicate": "human", "args": ["?x"]}]
}'
# Forget a fact
curl -X POST http://localhost:9300/forget \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: my-tenant" \
-d '{"predicate": "likes", "args": ["alice", "bob"]}'| Header | Required | Default | Purpose |
|---|---|---|---|
Content-Type | Yes | — | Must be application/json |
X-Database | No | default | Selects target database |
X-Tenant-ID | Yes (REST) | default (MCP only) | Tenant isolation. Throws `ValidationException` if missing on REST endpoints. |
X-API-Key | Conditional | — | Required when auth is LEGACY or RBAC mode |
pip install nocturnusaiimport asyncio
from nocturnusai import NocturnusAIClient
async def main():
client = NocturnusAIClient(
base_url="http://localhost:9300",
database="default",
tenant_id="my-tenant",
# api_key="your-key" # if auth enabled
)
# Assert a fact
await client.tell("likes", ["alice", "bob"])
# Query
results = await client.ask("likes", ["alice", "?who"])
print(results)
# Teach a rule
await client.teach(
head={"predicate": "mortal", "args": ["?x"]},
body=[{"predicate": "human", "args": ["?x"]}]
)
asyncio.run(main())Sync wrapper (for non-async contexts):
from nocturnusai import SyncNocturnusAIClient
client = SyncNocturnusAIClient(
base_url="http://localhost:9300",
database="default",
tenant_id="my-tenant",
)
client.tell("likes", ["alice", "bob"])npm install nocturnusai-sdkimport { NocturnusAIClient } from "nocturnusai-sdk";
const client = new NocturnusAIClient({
baseUrl: "http://localhost:9300",
database: "default",
tenantId: "my-tenant",
// apiKey: "your-key" // if auth enabled
});
// Assert a fact
await client.tell("likes", ["alice", "bob"]);
// Query
const results = await client.ask("likes", ["alice", "?who"]);
console.log(results);
// Teach a rule
await client.teach({
head: { predicate: "mortal", args: ["?x"] },
body: [{ predicate: "human", args: ["?x"] }],
});The TypeScript SDK has zero runtime dependencies (uses built-in fetch).
NocturnusAI supports three authentication modes:
| Mode | Env Vars Required | Header | Description |
|---|---|---|---|
| DISABLED | _(none)_ | _(none)_ | No authentication. All requests pass through. Default mode. |
| LEGACY | API_KEY=<key> | X-API-Key: <key> | Single static API key. All requests must include the key. |
| RBAC | AUTH_ENABLED=true | X-API-Key: <key> | Multi-key with roles and scoping. Keys created via API. |
Detect current mode:
curl http://localhost:9300/auth/statusReturns:
{
"mode": "DISABLED",
"authenticated": false,
"bootstrapped": false
}RBAC mode requires a bootstrap sequence to create the first admin key.
export AUTH_ENABLED=true
export NOCTURNUSAI_ADMIN_USER=admin
export NOCTURNUSAI_ADMIN_PASS=your-secure-password
./gradlew :nocturnusai-server:runcurl -X POST http://localhost:9300/auth/bootstrap \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "your-secure-password"
}'Response contains the admin API key:
{
"apiKey": "nocturnusai_ak_...",
"role": "ADMIN",
"description": "Bootstrap admin key"
}# Create a key scoped to a specific database and tenant
curl -X POST http://localhost:9300/auth/keys \
-H "Content-Type: application/json" \
-H "X-API-Key: nocturnusai_ak_..." \
-d '{
"name": "agent-key",
"role": "WRITE",
"databases": ["default"],
"tenants": ["my-tenant"]
}'curl -X POST http://localhost:9300/tell \
-H "Content-Type: application/json" \
-H "X-API-Key: nocturnusai_ak_agent_..." \
-H "X-Database: default" \
-H "X-Tenant-ID: my-tenant" \
-d '{"predicate": "status", "args": ["system", "online"]}'All NocturnusAI databases are multi-tenant. Each tenant gets an isolated LogicContext with its own Hexastore, rules, and memory.
The default database exists automatically. To create a new one:
curl -X POST http://localhost:9300/admin/databases \
-H "Content-Type: application/json" \
-d '{"name": "my-database"}'curl -X POST http://localhost:9300/admin/databases/default/tenants \
-H "Content-Type: application/json" \
-d '{"tenantId": "my-tenant"}'# Every request to tenant-scoped endpoints MUST include this header
curl -X POST http://localhost:9300/tell \
-H "Content-Type: application/json" \
-H "X-Database: default" \
-H "X-Tenant-ID: my-tenant" \
-d '{"predicate": "ready", "args": ["agent-1"]}'Important: X-Tenant-ID is required on most endpoints and throws a ValidationException if missing. MCP routes (POST /mcp) default to "default" when the header is absent, but REST routes do not.
Error: ValidationException: X-Tenant-ID header is required
Fix: Add X-Tenant-ID header to every REST request. MCP routes default to "default", but REST endpoints require it explicitly.
# Wrong
curl -X POST http://localhost:9300/tell -H "Content-Type: application/json" \
-d '{"predicate": "test", "args": ["a"]}'
# Correct
curl -X POST http://localhost:9300/tell -H "Content-Type: application/json" \
-H "X-Tenant-ID: default" \
-d '{"predicate": "test", "args": ["a"]}'Error: 401 Unauthorized when no auth is configured, or requests pass through when auth is expected.
Fix: Check the current auth mode:
curl http://localhost:9300/auth/statusEnsure the server env vars match your expectation:
API_KEY=xxx = LEGACY (single key)AUTH_ENABLED=true = RBAC (multi-key)Symptom: Claude Desktop fails to connect to NocturnusAI MCP server.
Cause: NocturnusAI uses the older HTTP+SSE MCP transport. Claude Desktop is moving toward Streamable HTTP transport.
Fix: Use the mcp-remote bridge which translates between stdio (what Claude Desktop expects) and HTTP+SSE (what NocturnusAI speaks):
npx mcp-remote http://localhost:9300/mcp/sseSee the .mcp.json example in Connection Path 1 for the full configuration with headers.
Symptom: Connection refused on port 9300.
Fix:
curl http://localhost:9300/healthPORT env var is setdocker ps to verify the container is upSymptom: Errors when asserting facts or querying.
Fix: Create the tenant first:
curl -X POST http://localhost:9300/admin/databases/default/tenants \
-H "Content-Type: application/json" \
-d '{"tenantId": "my-tenant"}'Symptom: 403 Forbidden with a valid-looking key.
Fix: Verify the key has the correct role and scoping:
curl http://localhost:9300/auth/whoami \
-H "X-API-Key: nocturnusai_ak_..."Check that the key's databases and tenants arrays include the database and tenant you are targeting.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.