OpenAF Advanced Guide

Deeper coverage of selected modules and patterns.

1. Scheduler (ow.server.scheduler)

ow.loadServer();
var sch = new ow.server.scheduler();
var id = sch.addEntry("*/10 * * * * *", () => log('tick'), true); // cron, fn, waitForFinish
// Modify later
id = sch.modifyEntry(id, "*/30 * * * * *", () => log('slower'), true);

Used internally by periodic oJobs with cron expressions.

2. Cron Utilities (ow.format.cron)

ow.loadFormat();
var info = ow.format.cron.howManyAgo("*/5 * * * * *", Date.now() - 20000);
// info.isDelayed, info.missedExecutions

Useful for custom reliability checks beyond built-in cronCheck.

3. Channels as Distributed Data Fabric

  • mem in‑memory
  • mvs persistent (MapDB / storage engine)
  • big peer-synchronizable

Pattern: create, expose, peer, cluster.

$ch('events').create();
$ch('events').set({id:1},{msg:'hello'});
$ch('events').subscribe((ch,op,k,v)=> log(op+':'+stringify(k)));

4. Channel Clustering Overview

Declared via ojob.channels.clusters enabling node discovery & replication. Each cluster entry initializes an ow.server.cluster with periodic verification; combine with persistent channel types for fault tolerance.

5. Secure Buckets (ow.sec)

ow.loadSec();
ow.sec.openMainSBuckets('masterSecret');
var bucket = $sec('repo','bucket','lock','masterSecret');
bucket.setSecret('bucket','lock','apiKey',{ key:'abc', created:new Date() });
var s = bucket.getSecret('bucket','lock','apiKey');

Supports multiple repos, lock-based access, encrypted at rest.

6. AI Helpers (ow.ai)

ow.loadAI();
var lr = ow.ai.regression().linear([[0,1],[1,3],[2,5]]);
log(lr.string);

Combine with (llm) built-in job for integrated prompt workflows; set OAF_MODEL or use provider-specific configs.

See also: ow-ai-gpttypes.md for provider wrappers, standardized interfaces and implementation notes.

7. Python Interop Patterns

Use server mode for throughput:

ow.loadPython();
ow.python.startServer();
for (var i=0;i<100;i++) ow.python.exec("x = " + i + "*2", {}, []);
ow.python.stopServer();

Maintain state across calls with execPM persistent map.

8. Timeout & Cancellation Composition

Blend $tb with external cancel signals:

var cancelled=false;
setTimeout(()=> cancelled=true, 3000);
var result = $tb(() => {
  while(!cancelled) {/* work */}
}).timeout(10000).stopWhen(()=>cancelled).exec();

9. Template Customization

Add namespaced helpers:

ow.loadTemplate();
ow.template.addHelpers('x', { upper: s => s.toUpperCase() });
print(templify('Hi {{x_upper name}}', { name:'dev' }));

10. Structured Logging Strategies

Set ojob.log.format: json or env OJOB_JSONLOG=true. Combine with external log forwarder reading stderr. Include correlation IDs by inserting into args early and referencing in templates.

11. Data Validation Chaining

_$(value,'label').toNumber().isNumber().between(1,100).default(10) provides readable declarative constraints. Attach to job check.in & check.out for consistent boundaries.

12. DSL Reduction with Built-ins

Favor built-in job shortcuts (if), (repeat), (each), (parallel) to minimize JS code, enabling easier reasoning by humans & LLMs while improving change auditability.

13. Performance Tuning Levers

  • Reduce startup: set ojob.conAnsi: false (skip terminal probing)
  • Limit thread count: ojob.numThreads
  • Adjust parallel heuristics: ojob.flags.PFOREACH.*
  • Prefer execRequire for large reusable code blocks vs inline duplicates

14. Asynchronous Execution with oPromise

  • $do – queue work on the standard ForkJoin-backed pool and receive an oPromise for fluent .then / .catch composition. The resolver passed into your function can resolve with returned values or explicit resolve() calls, while thrown errors or reject() calls route to the rejection chain.
  • $doV – same contract as $do but targets a virtual-thread-per-task executor so launching many concurrent tasks will not consume native threads when the JVM supports Project Loom virtual threads.
  • Coordination helpers – mix $doAll / $doFirst (wrappers over oPromise.all() / .race()) to wait for all tasks or the first completion, enabling fan-out/fan-in patterns without manual synchronization primitives.
  • Cancellation – call .cancel() on any $do / $doV promise to interrupt the associated thread (mirroring the Threads plugin) and drive the chain into the rejection path for cleanup.

Example fan-out flow using virtual threads:

var tasks = [url1, url2, url3].map(url =>
  $doV(() => httpGet(url))
);

$doAll(tasks)
  .then(results => log("Fetched: " + results.length))
  .catch(err => logErr("HTTP error: " + err));

Combine with $tb or custom cancellation logic for cooperative shutdown when outstanding promises should be abandoned.

15. Observability and Telemetry

Metrics Collection and Exposure

OpenAF provides comprehensive metrics capabilities through ow.metrics and integrated oJob telemetry:

// Load metrics module
ow.loadMetrics();

// Add custom metrics
ow.metrics.add("customGauge", () => ({ value: getCustomValue() }));
ow.metrics.add("requestCounter", () => ({ value: $get("requestCount") || 0 }));

// Start built-in collectors (CPU, memory, etc.)
ow.metrics.startCollecting();

// Get all metrics
var allMetrics = ow.metrics.getAll();

// Get specific metrics
var someMetrics = ow.metrics.getSome(["customGauge", "mem"]);

oJob Metrics Integration

ojob:
  metrics:
    # Passive metrics (HTTP endpoint)
    passive: true
    port   : 9101
    uri    : "/metrics"
    host   : "0.0.0.0"
    
    # Active metrics (push to external systems)
    add:
      customMetric: | #js
        return { value: Math.random() * 100 }
      processedItems: | #js
        return { value: $get("itemsProcessed") || 0 }
    
    # Push to OpenMetrics/Prometheus
    openmetrics:
      url: "http://pushgateway:9091/metrics/job/myapp"
      period: 30000
      metrics: ["customMetric", "mem", "cpu"]
    
    # Push to nAttrMon
    nattrmon:
      url: "http://nattrmon:8080/cvals"
      period: 60000
      metrics: ["customMetric", "processedItems"]
    
    # Collect to channel for historical analysis
    collect:
      active: true
      ch    : "metricsHistory"
      period: 10000

Server Telemetry Features

// Load server module
ow.loadServer();

// Passive telemetry endpoint
ow.server.telemetry.passive(9102, "/health", true, "myapp", {
  "requests_total": { 
    text: "Total requests", 
    help: "Number of requests processed", 
    type: "counter" 
  }
});

// Active telemetry pushing
ow.server.telemetry.active(function() {
  // Custom telemetry sender
  var metrics = ow.metrics.getAll();
  // Send to your monitoring system
  log("Sending metrics: " + JSON.stringify(metrics));
}, 30000);

// Send to nAttrMon
var sender = ow.server.telemetry.send2nAttrMon(
  "http://nattrmon:8080/cvals",
  "myapp",
  ["cpu", "mem", "customMetric"]
);
ow.server.telemetry.active(sender, 60000);

OpenMetrics Format Support

// Convert metrics to OpenMetrics format
var openMetrics = ow.metrics.fromObj2OpenMetrics(
  ow.metrics.getAll(),      // metrics object
  "myapp",                  // prefix
  new Date(),               // timestamp
  {                         // help text mapping
    "cpu": { text: "CPU usage percentage", type: "gauge" },
    "mem": { text: "Memory usage bytes", type: "gauge" }
  }
);

// Expose via HTTP server
var httpd = ow.server.httpd.start(9090);
ow.server.httpd.route(httpd, "/metrics", function(req) {
  return httpd.replyOKText(openMetrics);
});

Historical Metrics Collection

ojob:
  channels:
    create:
    - name: metricsHistory
      type: mvs
      options:
        file: metrics.db
  
  metrics:
    collect:
      active: true
      ch    : metricsHistory
      period: 5000  # Collect every 5 seconds
      some  : ["mem", "cpu", "customMetric"]  # Only specific metrics

Monitoring Integration Examples

# Prometheus integration
ojob:
  metrics:
    passive: true
    port   : 9101
    uri    : "/metrics"
    openmetrics: true
    prefix : "myapp"

# Custom alerting
jobs:
- name: "Health Check"
  type: periodic
  typeArgs:
    cron: "*/30 * * * * *"  # Every 30 seconds
  exec: |
    var metrics = ow.metrics.getAll();
    if (metrics.mem.value > 1000000000) {  # 1GB
      log("HIGH MEMORY USAGE: " + ow.format.toBytesAbbreviation(metrics.mem.value));
    }

16. Terminal Visualization Patterns

The ow.format terminal visualization layer is useful for CLIs, live status pages and compact operational output.

Capability-aware rendering

ow.loadFormat();

var caps = ow.format.term.getCapabilities();
var pal  = ow.format.term.getPalette("auto");

log("TTY=" + caps.isTTY + ", color=" + caps.colorMode + ", unicode=" + caps.unicode);

Renderers automatically degrade for non-TTY, low-color and non-Unicode environments, so prefer the semantic palette instead of hard-coded ANSI sequences.

Customizing colors with __colorFormat

__colorFormat is the global map holding the ANSI colors/styles used by colorify, cprint*, printTable, printTree, the ask* prompts and markdown rendering (ow.format.withMD). Each value is a comma-separated list of ansiColor attributes (e.g. "BOLD,CYAN").

Entry Purpose
key, number, string, boolean, date, default Colors per data type (maps, trees, tables)
askPre, askQuestion, askChoose, askChooseFilter, askPos Colors for ask, askChoose and askChooseMultiple
askChooseChars Characters for choose prompts (chooseMultipleSelected, chooseMultipleUnselected, chooseUp, chooseDown, chooseLine)
table { lines, value, title, bandRow, format }; format (table, stable, ctable, btable, bstable) is the default table renderer for $output when no global/explicit format is set
tree { lines } color of tree connectors
md Markdown theme: codeBlock, codeInline, heads (h1–h4), line, link, bullets, list, strike, note (missing entries get defaults on first use)
__colorFormat.string = "GREEN";
__colorFormat.table.title = "BOLD,UNDERLINE";
__colorFormat.md.heads.h1 = "CYAN,BOLD";
cprint({ name: "openaf", ok: true, n: 1 });

Side-line themes (ow.format.withSideLineThemes)

ow.format.withSideLine(text, width, lineColor, textColor, theme, extra) wraps text with a side line or box; ow.format.withSideLineThemes() returns the available themes by name (also used by __colorFormat.md.*.theme).

A theme is a map of characters: ltop, tmiddle, rtop (top border), lmiddle, rmiddle (sides), lbottom, bmiddle, rbottom (bottom border) and tab (tab replacement, default 3 spaces). Borders are only drawn when the corner characters are defined.

Family Themes
Plain sides simpleOneSpace, simpleLine, doubleOneSpace, doubleLine, doubleLineBothSides
Sides with tips simpleLineWithTips, simpleLineWithTopTip, simpleLineWithBottomTip, simpleLineWithCTips, simpleLineWithCTopTip, simpleLineWithCBottomTip, simpleLineWithRTips, simpleLineWithRTopTip, simpleLineWithRBottomTip, doubleLineWithRTips
Closed boxes closedOneSpace, closedRect, closedDoubleRect, closedCurvedRect
Open boxes openTopRect, openBottomRect, openRect, openTopCurvedRect, openBottomCurvedRect, openCurvedRect, openCurvedSpace, blockCurvedSpace
ow.loadFormat();
var t = ow.format.withSideLineThemes();
print(ow.format.withSideLine("Hello\nworld", 40, "YELLOW", __, t.closedCurvedRect, { header: " note ", headerAlign: "center" }));

For closed themes, extra accepts header, footer, headerAlign and footerAlign (left, center, right). Add your own themes (names that clash with built-in ones keep the built-in definition) by setting __flags.sideLineCustomThemes before calling withSideLineThemes():

__flags.sideLineCustomThemes = { arrow: { lmiddle: ">" } };
print(ow.format.withSideLine("custom", __, "GREEN", __, ow.format.withSideLineThemes().arrow));

Dashboard composition

print(ow.format.printDashboard([
  [
    { type: "bullet", title: "Storage", data: { value: 72, max: 100, target: 80 } },
    { type: "timeline", title: "Pipeline", data: [
      { label: "build", start: Date.now() - 120000, end: Date.now() - 60000, status: "ok" },
      { label: "test",  start: Date.now() - 60000, end: Date.now(),          status: "warn" }
    ] }
  ],
  [
    { type: "statusMatrix", title: "Fleet", data: { values: [["ok", "ok"], ["warn", "down"]], xLabels: ["a", "b"], yLabels: ["api", "db"] } }
  ]
], { width: 90, border: true }));

Use printDashboard(...) for explicit layouts and ow.format.string.grid(...) when you want a grid of cells that can mix classic renderers (map, tree, table, chart) with newer terminal widgets (sparkline, histogram, heatmap, bullet, scatter, boxplot, timeline, statusMatrix, progress, md, text).

Live loops

var live = ow.format.viz.live(function(ctx) {
  return ow.format.printDashboard([
    { type: "sparkline", title: "load", data: [ctx.frame % 7, 3, 5, 2, 6] },
    { type: "text", title: "size", data: stringify(ctx.size) }
  ], { width: ctx.size.width, height: 8 });
}, { fps: 5, diff: true });

The controller returned by viz.live(...) exposes start(), stop(), update() and stats(). Combine it with viz.watchResize(...) or the context size argument to keep output responsive.

17. AI and Machine Learning Integration

OpenAF provides comprehensive AI capabilities through ow.ai for both traditional ML and modern LLM integration.

Traditional Neural Networks

// Load AI module
ow.loadAI();

// Create a neural network
var nn = new ow.ai.network({ 
  type: "perceptron", 
  args: [2, 3, 1]  // 2 inputs, 3 hidden, 1 output
});

// Train the network
nn.train([
  {input: [0,0], output: [0]},
  {input: [0,1], output: [1]},
  {input: [1,0], output: [1]},
  {input: [1,1], output: [0]}
]);

// Use the network
var result = nn.get([1,0]);  // Returns ~1 for XOR

Statistical Analysis

// Regression analysis
var regression = ow.ai.regression();
var data = [[0,1],[1,3],[2,5],[3,7]];  // x,y pairs

var linear = regression.linear(data);
log("Equation: " + linear.string);     // y = 2x + 1
log("R²: " + linear.r2);               // Correlation coefficient

// Other regression types
var polynomial = regression.polynomial(data, { order: 2 });
var exponential = regression.exponential(data);

Statistical Values Array

// Create statistical tracker
var stats = ow.ai.valuesArray(100);  // Keep last 100 values

// Add values
stats.push(85.2);
stats.push(87.1);
stats.push(92.3);

// Get statistics
log("Average: " + stats.movingAverage());
log("Deviation: " + stats.deviation());
log("Variance: " + stats.variance());

Large Language Models (LLMs)

// Create LLM client (OpenAI example)
var llm = ow.ai.gpt({
  type: "openai",
  key : "your-api-key",
  url : "https://api.openai.com/v1",
  model: "gpt-3.5-turbo"
});

// Simple prompt
var response = llm.prompt("Explain quantum computing in simple terms");
log(response);

// Azure OpenAI v1 compatible endpoint
var azureV1 = ow.ai.gpt({
  type: "openai",
  key : "your-azure-api-key",
  url : "https://RESOURCE.openai.azure.com",
  mode: "azure-openai-v1",
  model: "your-deployment-name"
});

// Azure OpenAI legacy deployment endpoint
var azureLegacy = ow.ai.gpt({
  type       : "openai",
  key        : "your-azure-api-key",
  url        : "https://RESOURCE.openai.azure.com",
  mode       : "azure-openai-legacy",
  deployment : "your-deployment-name",
  apiVersion : "2024-10-21"
});

// Azure AI Foundry v1 endpoint
var foundry = ow.ai.gpt({
  type       : "openai",
  key        : "your-foundry-api-key",
  url        : "https://RESOURCE.services.ai.azure.com",
  mode       : "foundry",
  model      : "your-deployment-name"
});

// Azure AI Foundry dated model inference endpoint
var foundryPreview = ow.ai.gpt({
  type       : "openai",
  key        : "your-foundry-api-key",
  url        : "https://RESOURCE.services.ai.azure.com/models",
  mode       : "foundry",
  apiVersion : "2024-05-01-preview",
  model      : "your-deployment-name"
});

// Azure and Foundry modes default to api-key authentication.
// Standard OpenAI mode defaults to Authorization: Bearer.
// Use authType only for compatible proxies or custom authentication:
// authType: "bearer" | "api-key" | "none"

// Conversation
llm.addSystemPrompt("You are a helpful coding assistant");
llm.addUserPrompt("How do I sort an array in JavaScript?");
var answer = llm.prompt();

Multi-Provider LLM Support

// OpenAI
var openai = ow.ai.gpt({
  type: "openai",
  key : process.env.OPENAI_KEY,
  model: "gpt-4"
});

// Anthropic Claude
var claude = ow.ai.gpt({
  type: "anthropic", 
  key : process.env.ANTHROPIC_KEY,
  model: "claude-3-sonnet-20240229"
});

// Local Ollama
var ollama = ow.ai.gpt({
  type: "ollama",
  url : "http://localhost:11434",
  model: "llama2"
});

// Google Gemini
var gemini = ow.ai.gpt({
  type: "gemini",
  key : process.env.GEMINI_KEY,
  model: "gemini-2.5-flash"
});

LLM Tool/Function Calling

var llm = ow.ai.gpt({ type: "openai", key: "...", model: "gpt-3.5-turbo" });

// Register a tool
llm.setTool("getCurrentWeather", 
  "Get the current weather for a location",
  {
    type: "object",
    properties: {
      location: { type: "string", description: "City name" },
      units: { type: "string", enum: ["celsius", "fahrenheit"] }
    },
    required: ["location"]
  },
  function(params) {
    // Your weather API call here
    return { temperature: 22, condition: "sunny" };
  }
);

// Use the tool in conversation
var response = llm.prompt("What's the weather like in London?");

Image Processing with LLMs

var llm = ow.ai.gpt({ type: "openai", key: "...", model: "gpt-4-vision-preview" });

// Analyze an image
var description = llm.promptImage(
  "Describe what you see in this image",
  "/path/to/image.jpg",
  "high"  // detail level
);

// Generate images (OpenAI only)
var imageData = llm.promptImgGen("A sunset over mountains");
io.writeFileBytes("generated.png", imageData[0]);

oJob LLM Integration

# Built-in LLM job
jobs:
- name: "AI Analysis"
  from: "ojob llm"
  args:
    __llmPrompt: "Analyze this data and provide insights"
    __llmInPath: "data"
    __llmEnv   : "OPENAI_API_KEY"
    __llmOptions:
      type: "openai"
      model: "gpt-4"
      temperature: 0.3

- name: "Data Processing"
  exec: |
    args.data = { sales: [100, 200, 150], region: "North" };
  to: ["AI Analysis"]

Advanced LLM Patterns

// Conversation management
var llm = ow.ai.gpt({...});

// Save/restore conversations
var conversation = llm.getConversation();
$set("chatHistory", conversation);

// Later...
llm.setConversation($get("chatHistory"));

// JSON structured responses
var jsonResponse = llm.prompt(
  "Return product info as JSON", 
  "gpt-3.5-turbo",
  0.1,  // low temperature for consistency
  true  // JSON flag
);
var product = JSON.parse(jsonResponse);

// Batch processing
var results = [];
["item1", "item2", "item3"].forEach(item => {
  results.push(llm.prompt("Analyze: " + item));
});

AI Integration Best Practices

  1. API Key Management: Use environment variables and ow.sec for secure key storage
  2. Error Handling: LLM calls can fail; wrap in try-catch blocks
  3. Rate Limiting: Implement delays between calls to respect API limits
  4. Cost Control: Monitor usage and implement budget controls
  5. Prompt Engineering: Use clear, specific prompts for better results
  6. Conversation Memory: Manage conversation history to stay within token limits
// Example with error handling and retry
function robustLLMCall(prompt, maxRetries = 3) {
  var llm = ow.ai.gpt({...});
  
  for (var i = 0; i < maxRetries; i++) {
    try {
      return llm.prompt(prompt);
    } catch (e) {
      logWarn("LLM call failed (attempt " + (i+1) + "): " + e.message);
      if (i < maxRetries - 1) sleep(2000);  // Wait before retry
    }
  }
  throw "LLM call failed after " + maxRetries + " attempts";
}

18. Safe Dynamic Includes

Combine integrity hashes + authorized domains + change auditing flags. For local development disable with environment variable toggles but keep production strict.

19. HTTP Path Prefix Support (ow.server.httpd)

OpenAF’s embedded HTTP server supports serving all resources and routes under a configurable path prefix. This is useful when deploying behind a reverse proxy that forwards requests under a subpath (e.g. /app).

Setting a prefix

Set the HTTPD_PREFIX flag before starting any server. Key "0" is the global default for all ports; use a specific port number string to override per port:

// Set global prefix /app for all HTTP servers
__flags.HTTPD_PREFIX = { "0": "/app" };

// Or different prefixes per port
__flags.HTTPD_PREFIX = { "0": "", "8080": "/api", "9090": "/gui" };

Via oJob YAML:

ojob:
  flags:
    HTTPD_PREFIX:
      "0": /app

Prefix utility functions

ow.server.httpd provides helper functions to work with prefixes:

Function Description
ow.server.httpd.getPrefix(httpdOrPort) Returns the normalized prefix for the given server or port
ow.server.httpd.withPrefix(httpdOrPort, uri) Prepend the prefix to uri (skips absolute URLs)
ow.server.httpd.stripPrefix(httpdOrPort, uri) Remove the prefix from a URI for internal routing
ow.server.httpd.normalizePrefix(aPrefix) Normalize a raw prefix string (ensures leading /, no trailing /)
ow.loadServer();
__flags.HTTPD_PREFIX = { "0": "/app" };

var httpd = ow.server.httpd.start(8080);

// Build a prefixed URL
var link = ow.server.httpd.withPrefix(httpd, "/about"); // "/app/about"

// Strip prefix for internal route matching
ow.server.httpd.route(httpd, ow.server.httpd.mapWithExistingFn(httpd, {
  "/about": function(req) {
    return httpd.replyOKText("About page");
  }
}), function(req) {
  var internalURI = ow.server.httpd.stripPrefix(httpd, req.uri);
  return httpd.replyNotFound();
});

All built-in GUI pages and static-file handlers automatically respect the configured prefix.

20. MCP Client ($mcp)

$mcp(aOptions) creates a Model Context Protocol (MCP) client for communicating with LLM tool servers.

Connection types

type Description
stdio (default) Spawn a local process; communicate over stdin/stdout
remote / http HTTP JSON-RPC endpoint
sse HTTP endpoint with Server-Sent Events responses
ojob In-process oJob jobs exposed as MCP tools
dummy Local in-memory stub for testing
// stdio MCP server
var client = $mcp({ type: "stdio", cmd: "my-mcp-server" });
client.initialize();
var tools = client.listTools();
var result = client.callTool("myTool", { param: "value" });

// Remote HTTP MCP server
var remote = $mcp({ type: "remote", url: "https://mcp.example.com/mcp" });
remote.initialize();

Authentication (auth option)

For remote/http/sse connections:

// Static bearer token
var client = $mcp({
  type: "remote",
  url: "https://mcp.example.com/mcp",
  auth: { type: "bearer", token: "my-token" }
});

// OAuth2 client credentials
var client = $mcp({
  type: "remote",
  url: "https://mcp.example.com/mcp",
  auth: {
    type: "oauth2",
    tokenURL: "https://auth.example.com/oauth/token",
    clientId: "my-client",
    clientSecret: "my-secret",
    scope: "mcp:read mcp:write"
  }
});

// OAuth2 authorization_code (opens browser)
var client = $mcp({
  type: "remote",
  url: "https://mcp.example.com/mcp",
  auth: {
    type: "oauth2",
    grantType: "authorization_code",
    authURL: "https://auth.example.com/authorize",
    tokenURL: "https://auth.example.com/oauth/token",
    clientId: "my-client",
    redirectURI: "http://localhost:8080/callback",
    disableOpenBrowser: false  // set true to suppress browser launch
  }
});

OAuth2 token URLs can also be auto-discovered from the MCP server’s OAuth 2.0 Protected Resource Metadata when tokenURL/authURL are omitted.

To retain OAuth credentials across sessions, opt in with auth.tokenStore: { type: "sec", profile: "alice" }. The built-in adapter uses an encrypted SBucket and coordinates token refresh across local processes. Custom synchronous stores are also supported. See persistent OAuth, callback login and token stores for configuration, failure behavior and storage limitations.

Tool blacklist

Prevent specific tools from appearing in listTools() or being called via callTool():

var client = $mcp({
  type: "stdio",
  cmd: "my-mcp-server",
  blacklist: ["dangerousTool", "internalTool"]
});
client.initialize();
// listTools() will not include blacklisted tools
// callTool("dangerousTool", {}) throws an error

See also: ojob-security.md, openaf-flags.md, and main references.


This site uses Just the Docs, a documentation theme for Jekyll.