TCP & WebSocket
TCP synchronous request
Connect, send, read response, close — in a single call. Works in all modes.
- AssemblyScript
- Go
- Rust
import { tcpRequest } from "@luminarys/sdk-as";
// Plain TCP
const result = tcpRequest(
"redis:6379",
Uint8Array.wrap(String.UTF8.encode("PING\r\n")),
);
const text = String.UTF8.decode(result.buffer);
// TLS (verified)
const result2 = tcpRequest(
"api.example.com:443",
Uint8Array.wrap(String.UTF8.encode("GET / HTTP/1.0\r\nHost: api.example.com\r\n\r\n")),
true, // tls
);
// TLS (self-signed cert, dev only)
const result3 = tcpRequest(
"localhost:9443",
Uint8Array.wrap(String.UTF8.encode("GET /ping HTTP/1.1\r\nHost: localhost\r\n\r\n")),
true, // tls
true, // insecure
);
// Plain TCP
result, err := sdk.TcpRequest(sdk.TcpRequestOptions{
Addr: "redis:6379",
Data: []byte("PING\r\n"),
})
// TLS (verified)
result, err := sdk.TcpRequest(sdk.TcpRequestOptions{
Addr: "api.example.com:443",
Data: []byte("GET / HTTP/1.0\r\nHost: api.example.com\r\n\r\n"),
TLS: true,
})
// TLS (self-signed cert, dev only)
result, err := sdk.TcpRequest(sdk.TcpRequestOptions{
Addr: "localhost:9443",
Data: []byte("GET /ping HTTP/1.1\r\nHost: localhost\r\n\r\n"),
TLS: true,
Insecure: true,
})
// Plain TCP
let result = tcp_request(&TcpRequestOptions {
addr: "redis:6379".into(),
data: b"PING\r\n".to_vec(),
..Default::default()
})?;
// TLS (verified)
let result = tcp_request(&TcpRequestOptions {
addr: "api.example.com:443".into(),
data: b"GET / HTTP/1.0\r\nHost: api.example.com\r\n\r\n".to_vec(),
tls: true,
..Default::default()
})?;
// TLS (self-signed cert, dev only)
let result = tcp_request(&TcpRequestOptions {
addr: "localhost:9443".into(),
data: b"GET /ping HTTP/1.1\r\nHost: localhost\r\n\r\n".to_vec(),
tls: true,
insecure: true,
..Default::default()
})?;
TcpRequestOptions:
| Field | Type | Default | Description |
|---|---|---|---|
addr | string | required | Host:port to connect to |
data | bytes | required | Payload to send |
tls | bool | false | Use TLS encryption |
insecure | bool | false | Skip TLS certificate verification (dev only) |
timeout_ms | int | 0 (30s) | Total timeout for connect+send+read |
max_bytes | int | 0 (1 MB) | Max response size |
TcpRequestResult:
| Field | Type | Description |
|---|---|---|
data | bytes | Response payload |
TCP persistent connection
Callback-based TCP requires the built-in agent. In MCP mode, use tcpRequest instead.
Push-model: incoming data is delivered to a callback method. tcpClose can be safely called from inside the callback.
- AssemblyScript
- Go
- Rust
import { tcpConnect, tcpWrite, tcpClose } from "@luminarys/sdk-as";
// Plain TCP
const connId = tcpConnect("redis:6379", "on_data");
// TLS (verified)
const connId2 = tcpConnect("api.example.com:443", "on_data", true);
// TLS (self-signed, dev only)
const connId3 = tcpConnect("localhost:9443", "on_data", true, true);
// Send data
tcpWrite(connId, Uint8Array.wrap(String.UTF8.encode("PING\r\n")));
// Close (safe to call from callback)
tcpClose(connId);
// Plain TCP
connID, err := sdk.TcpConnect(sdk.TcpConnectOptions{
Addr: "redis:6379", Callback: "on_data",
})
// TLS (verified)
connID, err := sdk.TcpConnect(sdk.TcpConnectOptions{
Addr: "api.example.com:443", Callback: "on_data", TLS: true,
})
// TLS (self-signed, dev only)
connID, err := sdk.TcpConnect(sdk.TcpConnectOptions{
Addr: "localhost:9443", Callback: "on_data", TLS: true, Insecure: true,
})
// Send data
sdk.TcpWrite(connID, []byte("PING\r\n"))
// Close (safe to call from callback)
sdk.TcpClose(connID)
// Plain TCP
let conn_id = tcp_connect(TcpConnectOptions {
addr: "redis:6379".into(), callback: "on_data".into(),
..Default::default()
})?;
// TLS (verified)
let conn_id = tcp_connect(TcpConnectOptions {
addr: "api.example.com:443".into(), callback: "on_data".into(), tls: true,
..Default::default()
})?;
// TLS (self-signed, dev only)
let conn_id = tcp_connect(TcpConnectOptions {
addr: "localhost:9443".into(), callback: "on_data".into(),
tls: true, insecure: true,
..Default::default()
})?;
// Send data
tcp_write(&conn_id, b"PING\r\n".to_vec())?;
// Close (safe to call from callback)
tcp_close(&conn_id)?;
TcpConnectOptions:
| Field | Type | Default | Description |
|---|---|---|---|
addr | string | required | Host:port to connect to |
callback | string | "" | Skill method name for incoming data (empty = drain) |
tls | bool | false | Use TLS encryption |
insecure | bool | false | Skip TLS certificate verification (dev only) |
server_name | string | "" | Override TLS SNI hostname (empty = use host from addr) |
timeout_ms | int | 0 (30s) | Dial timeout |
Callback signature
The callback method receives a ConnEvent with connection ID, data, and error info. Use @skill:callback to hide from MCP.
- AssemblyScript
- Go
- Rust
import { tcpClose, ERROR_KIND_EOF } from "@luminarys/sdk-as";
// @skill:callback
// @skill:method on_data "TCP read callback."
// @skill:param conn_id required "Connection ID"
// @skill:param data optional "Received bytes" type:bytes
// @skill:param error_kind optional "Error kind"
// @skill:param error_msg optional "Error message"
export function onData(_ctx: Context, conn_id: string, data: Uint8Array,
error_kind: string, error_msg: string): string {
if (data.length > 0) {
// process data...
const text = String.UTF8.decode(data.buffer);
}
if (error_kind == ERROR_KIND_EOF) {
tcpClose(conn_id); // safe from callback
}
return "ok";
}
// @skill:callback
// @skill:method on_data "TCP read callback."
// @skill:param conn_id required "Connection ID"
// @skill:param data optional "Received bytes"
// @skill:param error_kind optional "Error kind"
// @skill:param error_msg optional "Error message"
func OnData(_ *sdk.Context, connID string, data []byte,
errorKind string, errorMsg string) (string, error) {
if len(data) > 0 {
// process data...
text := string(data)
_ = text
}
if errorKind == string(sdk.ErrorKindEOF) {
sdk.TcpClose(connID) // safe from callback
}
return "ok", nil
}
/// @skill:callback
/// @skill:method on_data "TCP read callback."
/// @skill:param conn_id required "Connection ID"
/// @skill:param data optional "Received bytes" type:bytes
/// @skill:param error_kind optional "Error kind"
/// @skill:param error_msg optional "Error message"
pub fn on_data(_ctx: &mut Context, conn_id: String, data: Vec<u8>,
error_kind: String, error_msg: String) -> Result<String, SkillError> {
if !data.is_empty() {
// process data...
let text = String::from_utf8_lossy(&data);
}
if error_kind == "eof" {
let _ = tcp_close(&conn_id); // safe from callback
}
Ok("ok".into())
}
ConnEvent fields (delivered as method parameters):
| Field | Type | Description |
|---|---|---|
conn_id | string | Connection ID |
data | bytes | Received data (empty on error events) |
error_kind | string | Error classification (empty on data events) |
error_msg | string | Error details (empty on data events) |
Error kinds (delivered to callback when connection fails):
| Constant | Value | Description |
|---|---|---|
ERROR_KIND_NONE / ErrorKindNone / ErrorKind::None | "" | Data event — no error |
ERROR_KIND_EOF / ErrorKindEOF / ErrorKind::Eof | "eof" | Remote peer closed gracefully |
ERROR_KIND_RESET / ErrorKindReset / ErrorKind::Reset | "reset" | Connection reset by peer |
ERROR_KIND_TIMEOUT / ErrorKindTimeout / ErrorKind::Timeout | "timeout" | Read deadline exceeded |
ERROR_KIND_TLS / ErrorKindTLS / ErrorKind::Tls | "tls" | TLS handshake or record error |
ERROR_KIND_IO / ErrorKindIO / ErrorKind::Io | "io" | Generic I/O error |
WebSocket
WebSocket requires the built-in agent. Not available in MCP mode.
Connect, send, close
- AssemblyScript
- Go
- Rust
import { wsConnect, wsSend, wsClose, HttpHeader, WS_MESSAGE_TEXT } from "@luminarys/sdk-as";
const h = new HttpHeader(); h.name = "Authorization"; h.value = "Bearer token123";
const connId = wsConnect("wss://api.example.com/ws", [h], 0, "on_ws_msg");
// Send text message
wsSend(connId, Uint8Array.wrap(String.UTF8.encode('{"type":"subscribe"}')), WS_MESSAGE_TEXT);
// Close with normal code
wsClose(connId, 1000, "normal closure");
connID, err := sdk.WsConnect(
"wss://api.example.com/ws",
[]sdk.Header{{Name: "Authorization", Value: "Bearer token123"}},
0,
"on_ws_msg",
false,
)
sdk.WsSend(connID, []byte(`{"type":"subscribe"}`), sdk.WsMessageText)
sdk.WsClose(connID, 1000, "normal closure")
let conn_id = ws_connect(
"wss://api.example.com/ws",
vec![Header::new("Authorization", "Bearer token123")],
0,
"on_ws_msg",
false,
)?;
ws_send(&conn_id, br#"{"type":"subscribe"}"#.to_vec(), WS_MESSAGE_TEXT)?;
ws_close(&conn_id, 1000, "normal closure")?;
wsConnect parameters:
| Field | Type | Default | Description |
|---|---|---|---|
url | string | required | WebSocket URL (ws:// or wss://) |
headers | Header[] | [] | Request headers (e.g. Authorization) |
timeout_ms | int | 0 (30s) | Handshake timeout |
callback | string | "" | Skill method name for incoming messages |
insecure | bool | false | Skip TLS certificate verification for wss:// (dev only) |
Message type constants
| Constant | Value | Description |
|---|---|---|
WS_MESSAGE_TEXT / WsMessageText / WS_MESSAGE_TEXT | "text" | UTF-8 text message |
WS_MESSAGE_BINARY / WsMessageBinary / WS_MESSAGE_BINARY | "binary" | Binary message |
WS_MESSAGE_CLOSE / WsMessageClose / WS_MESSAGE_CLOSE | "close" | Close frame received |
Callback signature
The callback receives a WsEvent with the message data. Use @skill:callback to hide from MCP.
- AssemblyScript
- Go
- Rust
import { wsClose, WS_MESSAGE_TEXT, WS_MESSAGE_CLOSE, ERROR_KIND_EOF } from "@luminarys/sdk-as";
// @skill:callback
// @skill:method on_ws_msg "WebSocket callback."
// @skill:param conn_id required "Connection ID"
// @skill:param data optional "Message payload" type:bytes
// @skill:param message_type optional "text, binary, or close"
// @skill:param close_code optional "Close status code"
// @skill:param close_text optional "Close reason"
// @skill:param error_kind optional "Error kind"
// @skill:param error_msg optional "Error details"
export function onWsMsg(_ctx: Context, conn_id: string, data: Uint8Array,
message_type: string, close_code: i64, close_text: string,
error_kind: string, error_msg: string): string {
if (message_type == WS_MESSAGE_TEXT) {
const text = String.UTF8.decode(data.buffer);
// process text message...
}
if (message_type == WS_MESSAGE_CLOSE || error_kind == ERROR_KIND_EOF) {
// connection closing
}
return "ok";
}
// @skill:callback
// @skill:method on_ws_msg "WebSocket callback."
// @skill:param conn_id required "Connection ID"
// @skill:param data optional "Message payload"
// @skill:param message_type optional "text, binary, or close"
// @skill:param close_code optional "Close status code"
// @skill:param close_text optional "Close reason"
// @skill:param error_kind optional "Error kind"
// @skill:param error_msg optional "Error details"
func OnWsMsg(_ *sdk.Context, connID string, data []byte,
messageType string, closeCode int64, closeText string,
errorKind string, errorMsg string) (string, error) {
if messageType == sdk.WsMessageText {
text := string(data)
// process text message...
_ = text
}
if messageType == sdk.WsMessageClose || errorKind == string(sdk.ErrorKindEOF) {
// connection closing
}
return "ok", nil
}
/// @skill:callback
/// @skill:method on_ws_msg "WebSocket callback."
/// @skill:param conn_id required "Connection ID"
/// @skill:param data optional "Message payload" type:bytes
/// @skill:param message_type optional "text, binary, or close"
/// @skill:param close_code optional "Close status code"
/// @skill:param close_text optional "Close reason"
/// @skill:param error_kind optional "Error kind"
/// @skill:param error_msg optional "Error details"
pub fn on_ws_msg(_ctx: &mut Context, conn_id: String, data: Vec<u8>,
message_type: String, _close_code: i64, _close_text: String,
_error_kind: String, _error_msg: String) -> Result<String, SkillError> {
if message_type == WS_MESSAGE_TEXT {
let text = String::from_utf8_lossy(&data);
// process text message...
}
Ok("ok".into())
}
WsEvent fields (delivered as method parameters):
| Field | Type | Description |
|---|---|---|
conn_id | string | Connection ID |
data | bytes | Message payload (empty on close/error) |
message_type | string | "text", "binary", or "close" |
close_code | int | WebSocket close status code (only when message_type = "close") |
close_text | string | Close reason text (only when message_type = "close") |
error_kind | string | Error classification (same constants as TCP) |
error_msg | string | Error details |
Permissions
tcp:
enabled: true
allowlist:
- "redis:6379"
- "*.internal:*"
http:
enabled: true
allow_websocket: true
allowlist:
- "wss://api.example.com/**"
- TCP:
tcp.enabled: true, optionallytcp.allowlistwithhost:portpatterns - WebSocket:
http.enabled: true+http.allow_websocket: truewith WebSocket URL inhttp.allowlist