Skip to content
Merged

T8 #1785

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
707cea6
Add browser-assisted OAuth2 authorization_code flow to $mcp auth
nmaguiar Mar 12, 2026
b139fda
feat(jsonrpc): add SSE support and improve auth handling
nmaguiar Mar 12, 2026
295cf57
test(A2A::Client Remote SSE): add remote SSE client test
nmaguiar Mar 12, 2026
b8300b3
Merge pull request #1770 from OpenAF/codex/add-oauth2-authentication-…
nmaguiar Mar 12, 2026
bdfaf1e
Fix MCP SSE session header propagation
nmaguiar Mar 14, 2026
52712ed
feat(mcp): add tool blacklist functionality to prevent access to spec…
nmaguiar Mar 14, 2026
50747f3
feat(tests): add client tool blacklist test to validate tool access r…
nmaguiar Mar 14, 2026
9438d3d
Merge pull request #1779 from OpenAF/codex/check-mcp-client-for-sse-bugs
nmaguiar Mar 14, 2026
e28ae9a
chore(deps): update jsch dependency to version 2.27.9
nmaguiar Mar 14, 2026
71b2959
Merge branch 't8' of https://github.com/openaf/openaf into t8
nmaguiar Mar 14, 2026
18e333a
feat(markdown): add SVG rendering support
nmaguiar Mar 16, 2026
8633f17
chore(deps): upgrade kotlin-stdlib to 2.3.20
nmaguiar Mar 18, 2026
6a75381
feat(oJob): enhance argument handling with token resolution and unesc…
nmaguiar Mar 18, 2026
ddda5ec
Refactor URI handling to support dynamic prefixes across templates an…
nmaguiar Mar 20, 2026
060ae58
feat(docs): add MCP client section and SVG rendering flag to document…
nmaguiar Mar 20, 2026
ff67959
feat(ftp): add FTP/FTPS plugin with JavaScript wrapper
nmaguiar Mar 24, 2026
5e1545d
fix(ftp): improve directory listing with MLSD fallback and null safety
nmaguiar Mar 24, 2026
289119d
docs: update docs/* for new features (FTP, HTTPD_PREFIX, token interp…
Copilot Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ Use `$path` expressions in templates for dynamic data extraction, and leverage b
- **Enhanced Parameter Validation**: Comprehensive `_$()` validation chains with type conversion, regex matching, and range checking
- **Job Input/Output Validation**: Declarative `check.in` and `check.out` sections with fluent validation syntax
- **AI/LLM Integration**: Multi-provider LLM support (OpenAI, Anthropic, Gemini, Ollama) with function calling and image processing
- **MCP Client**: `$mcp()` for Model Context Protocol communication with stdio, HTTP, and SSE transports; OAuth2 (client credentials & authorization_code); tool blacklist (see `openaf-advanced.md` §19)
- **FTP/FTPS Client**: `$ftp()` shortcut for plain and TLS-secured FTP file transfers (see `openaf.md`)
- **HTTP Path Prefix**: Deploy the embedded HTTP server under a configurable subpath via `HTTPD_PREFIX` flag (see `openaf-advanced.md` §18, `openaf-flags.md`)
- **Telemetry & Metrics**: Built-in metrics collection, OpenMetrics format support, and integration with monitoring systems
- **Security Enhancements**: File integrity checking, authorized domains, and comprehensive audit trails
- **Async Promises**: `$do` / `$doV` helpers build on `oPromise` for threaded or virtual-thread asynchronous execution with familiar `.then` / `.catch` chaining.【F:js/openaf.js†L13130-L13157】【F:js/openaf.js†L12145-L12163】【F:js/openaf.js†L12208-L12251】
- **Async Promises**: `$do` / `$doV` helpers build on `oPromise` for threaded or virtual-thread asynchronous execution with familiar `.then` / `.catch` chaining.
- **Inline Argument Token Interpolation**: oJob args support `"${key}"` and `"prefix-${key}-suffix"` patterns with default values and backslash escaping (see `ojob.md`)

---
This index is intentionally minimal—open individual docs for full tables of contents.
4 changes: 3 additions & 1 deletion docs/ojob.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,9 @@ todo:
- **Inheritance**: These processed arguments are passed to the target job
- **Template integration**: Works seamlessly with oJob's template processing
- **Nested support**: Supports dot notation for nested object properties
- **Type preservation**: Values are processed as strings but maintain their intended types
- **Type preservation**: When a string value is exactly `"${key}"` the resolved value keeps its original type (number, boolean, array, object, etc.). When a token appears inside a longer string (e.g. `"prefix-${key}-suffix"`) the result is always a string.
- **Inline interpolation**: Tokens can appear anywhere inside a string value and multiple tokens may be combined — e.g. `"${host:-localhost}:${port:-8080}"`. Note that when multiple tokens are interpolated into a string context, each resolved value is converted via `String()` before concatenation.
- **Escaping**: Prefix a token with `\` to prevent resolution — `\${key}` is left as the literal string `${key}`. Use `\\${key}` to produce `\${key}` in the output (odd number of backslashes escapes, even number does not).

**Usage examples:**
```yaml
Expand Down
141 changes: 141 additions & 0 deletions docs/openaf-advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,5 +516,146 @@ function robustLLMCall(prompt, maxRetries = 3) {
## 17. Safe Dynamic Includes
Combine integrity hashes + authorized domains + change auditing flags. For local development disable with environment variable toggles but keep production strict.

## 18. 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:

```javascript
// 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:
```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 `/`) |

```javascript
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.

## 19. 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 |

```javascript
// 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:

```javascript
// 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.

### Tool blacklist

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

```javascript
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.
11 changes: 9 additions & 2 deletions docs/openaf-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,16 @@ Central list of noteworthy runtime flags and environment variables.

## Server Management

| Env | Default | Purpose |
|-----|---------|---------|
| Env / Flag | Default | Purpose |
|------------|---------|---------|
| OAF_PIDFILE | (unset) | Override PID file path in ow.server.checkIn |
| HTTPD_PREFIX *(flag)* | `{}` | Runtime flag (set via `__flags.HTTPD_PREFIX` or `ojob.flags`). Map of port-to-path-prefix entries for the embedded HTTP server. Key `"0"` is the global default. E.g. `{ "0": "/app", "8080": "/api" }`. Must be set before calling `ow.server.httpd.start()`. |

## Template / Markdown

| Flag | Default | Purpose |
|------|---------|---------|
| MD_RENDER_SVG | false | When true, ` ```svg ` fenced blocks in markdown are extracted and injected as inline SVG in HTML output instead of being treated as code blocks |

## Misc Performance / Behavior

Expand Down
59 changes: 59 additions & 0 deletions docs/openaf.md
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,65 @@ Convenience getters:
- `getJsSlon(i)` parse SLON
- `getYaml(i)` parse YAML

### $ftp - FTP / FTPS Client

`$ftp(aMap)` creates an FTP or FTPS client connection. `aMap` can be a URL string or a configuration map.

**URL format:**
```
ftp://user:pass@host:port/?timeout=5000&passive=true&binary=true
ftps://user:pass@host:port/?timeout=5000&passive=true&implicit=false&protocol=TLS
```

**Map format:**

| Key | Default | Description |
|-----|---------|-------------|
| `host` | – | Remote hostname or IP |
| `port` | `21` / `990` | Port (990 for implicit FTPS) |
| `login` | – | Username |
| `pass` | – | Password |
| `secure` | `false` | Enable FTPS (explicit TLS) |
| `implicit` | `false` | Use implicit TLS mode (port 990) |
| `protocol` | `"TLS"` | TLS protocol name |
| `passive` | `true` | Use passive mode |
| `binary` | `true` | Binary transfer mode |
| `timeout` | – | Connection timeout in ms |

**Available methods (all return `$ftp` for chaining except where noted):**

| Method | Description |
|--------|-------------|
| `cd(aPath)` | Change remote working directory |
| `pwd()` | Return current remote working directory (String) |
| `mkdir(aDir)` | Create remote directory |
| `getFile(src, dst)` | Download remote `src` to local `dst` |
| `putFile(src, dst)` | Upload local `src` (path or stream) to remote `dst` |
| `rename(src, dst)` | Rename / move remote file |
| `listFiles(aPath)` | Return array of file-info maps for `aPath` |
| `rm(aFilePath)` | Delete remote file |
| `rmdir(aFilePath)` | Delete remote directory |
| `passive(bool)` | Toggle passive mode |
| `binary(bool)` | Toggle binary transfer mode |
| `timeout(ms)` | Set connection timeout |
| `close()` | Close the connection |

**Examples:**
```javascript
// Plain FTP via URL
var ftp = $ftp("ftp://user:secret@ftp.example.com/");
ftp.cd("/uploads")
.putFile("/local/report.csv", "report.csv")
.close();

// FTPS via map
var ftps = $ftp({ host: "ftp.example.com", secure: true,
login: "user", pass: "secret" });
var files = ftps.listFiles("/data");
ftps.getFile("/data/report.csv", "/tmp/report.csv");
ftps.close();
```

### $tb - Thread Box (Timeout / Stop Controller)

Run a function with enforced timeout or stop condition:
Expand Down
16 changes: 8 additions & 8 deletions hbs/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0"/>
<title>{{title}}</title>

<link href="css/materialize-icon.css" rel="stylesheet">
<link href="css/materialize.css" type="text/css" rel="stylesheet" media="screen,projection"/>
<link href="{{uriPrefix}}/css/materialize-icon.css" rel="stylesheet">
<link href="{{uriPrefix}}/css/materialize.css" type="text/css" rel="stylesheet" media="screen,projection"/>
<!--link href="css/style.css" type="text/css" rel="stylesheet" media="screen,projection"/-->
</head>
<body>
Expand All @@ -28,11 +28,11 @@
</ul>

<!-- Scripts-->
<script src="js/jquery.js"></script>
<script src="js/materialize.js"></script>
<script src="js/stream.js"></script>
<script src="js/underscore.js"></script>
<script src="js/nlinq.js"></script>
<script src="{{uriPrefix}}/js/jquery.js"></script>
<script src="{{uriPrefix}}/js/materialize.js"></script>
<script src="{{uriPrefix}}/js/stream.js"></script>
<script src="{{uriPrefix}}/js/underscore.js"></script>
<script src="{{uriPrefix}}/js/nlinq.js"></script>

<div class="section">
<div class="container no-pad-bot s12">
Expand All @@ -55,7 +55,7 @@
$(".sidenav").sidenav();
});
window.onbeforeunload = function(event) {
$.ajax({ async: false, type: 'GET', url: '/exec/quit', success: function(d) {}});
$.ajax({ async: false, type: 'GET', url: '{{uriPrefix}}/exec/quit', success: function(d) {}});
}
</script>

Expand Down
14 changes: 7 additions & 7 deletions hbs/md.hbs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="/css/github-gist.css">
<link rel="stylesheet" href="/css/github-markdown.css">
<link rel="stylesheet" href="/css/materialize-icon.css">
<link rel="stylesheet" href="/css/nJSMap.css">
<link rel="stylesheet" href="{{uriPrefix}}/css/github-gist.css">
<link rel="stylesheet" href="{{uriPrefix}}/css/github-markdown.css">
<link rel="stylesheet" href="{{uriPrefix}}/css/materialize-icon.css">
<link rel="stylesheet" href="{{uriPrefix}}/css/nJSMap.css">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<script src="/js/highlight.js"></script>
<script src="{{uriPrefix}}/js/highlight.js"></script>
<script>hljs.highlightAll();</script>
<style>.markdown-body, .markdown-body-dark { box-sizing: border-box;{{#if noMaxWidth}}width: 100%;{{else}}max-width: 980px;{{/if}} margin: 0 auto; padding: 45px; } @media (max-width: 767px) { .markdown-body, .markdown-body-dark { padding: 15px; } }</style>
{{#each extras}}
Expand Down Expand Up @@ -57,9 +57,9 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', eve
{{{markdown}}}
{{#if themedark}}<script>document.querySelectorAll('pre code').forEach((block) => { block.classList.add('hljs_dark') })</script>{{/if}}
{{#if themeauto}}<script>if (__isDark) document.querySelectorAll('pre code').forEach((block) => { block.classList.add('hljs_dark') })</script>{{/if}}
{{#if mdcodeclip}}<script src="/js/mdcodeclip.js"></script>{{/if}}
{{#if mdcodeclip}}<script src="{{uriPrefix}}/js/mdcodeclip.js"></script>{{/if}}
{{#each posextras}}
{{{this}}}
{{/each}}
</body>
</html>
</html>
4 changes: 2 additions & 2 deletions hbs/odoc.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

var output = "";
if (newList.length == 1 || typeof newTerm !== 'undefined') {
$.getJSON("/odocKey?q=" + _.escape(searchVal), function(elem) {
$.getJSON("{{uriPrefix}}/odocKey?q=" + _.escape(searchVal), function(elem) {
var out = "";
out += "<span class=\"card-title thin\">" + elem.key + "</span>";
out += "<div class=\"card-action\"><h5 class=\"grey-text text-darken-1\">" + elem.fullkey + "</h5>";
Expand Down Expand Up @@ -80,4 +80,4 @@

<script>
update();
</script>
</script>
4 changes: 2 additions & 2 deletions hbs/opacks.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
<table class="grey-text text-darken-1">
{{#if Local}}
<tr><td class="align-top right-align"><b>Path:</b></td><td>{{Path}}</td></tr>
<tr><td class="align-top right-align"><b>Version:</b></td><td>{{Version}}{{#if Update}} (new version {{RemoteVersion}} available) &nbsp;&nbsp; <a href="/exec/opack/update?opack={{Name}}" class="waves-effect waves-light orange btn"><i class="material-icons left">cloud_download</i>Update</a>{{/if}}</td></tr>
<tr><td class="align-top right-align"><b>Version:</b></td><td>{{Version}}{{#if Update}} (new version {{RemoteVersion}} available) &nbsp;&nbsp; <a href="{{../uriPrefix}}/exec/opack/update?opack={{Name}}" class="waves-effect waves-light orange btn"><i class="material-icons left">cloud_download</i>Update</a>{{/if}}</td></tr>
{{else}}
<tr><td class="align-top right-align"><b>Version available:</b></td><td>{{RemoteVersion}}</td></tr>
{{/if}}
Expand All @@ -59,7 +59,7 @@
<tr><td class="align-top right-align"><b>Bug reporting:</b></td><td><a href="{{Bugs}}">{{Bugs}}</a></td></tr>
{{#if Local}}
{{else}}
<tr><td class="align-top right-align"><b></b></td><td><a href="/exec/opack/install?opack={{Name}}" class="waves-effect waves-light orange btn"><i class="material-icons left">cloud_download</i>Install</a></td></tr>
<tr><td class="align-top right-align"><b></b></td><td><a href="{{../uriPrefix}}/exec/opack/install?opack={{Name}}" class="waves-effect waves-light orange btn"><i class="material-icons left">cloud_download</i>Install</a></td></tr>
{{/if}}
</table>
</div>
Expand Down
Loading
Loading