Connectors
Reach real HTTP APIs and run sandboxed logic. Connectors are declared as data with brokered credentials.
A connector is how a Noodle Seed server reaches a backing system: a SaaS API, a first-party service, or your own API. Connectors are declared as data, and their credentials are resolved by the platform's credential broker, never hard-coded.
Build one with the connector builder, then bind it on the server's use map so tools can call it.
HTTP connectors
connector(id).version(v).http({...}) describes a real HTTP API: a baseUrl, an auth block, and an
operations map. Requests and responses map with ${...} expressions.
import { server, tool, connector, secret, z } from '@noodleseed/one';
const weather = connector('weather')
.version('1.0.0')
.http({
baseUrl: 'https://api.example.com',
allowedOrigins: ['https://api.example.com'],
auth: { kind: 'apiKey', header: 'X-Api-Key', secret: secret('WEATHER_API_KEY') },
operations: {
current: {
type: 'read',
method: 'GET',
path: '/v1/weather',
query: { city: '${input.city}' },
input: z.object({ city: z.string() }),
output: z.object({ temp_c: z.number() }),
response: { temp_c: '${response.temperature}' },
},
},
});
export default server('weather', { title: 'Weather', version: '1.0.0', use: { weather } }, [
tool('current_weather', {
description: 'Get the current temperature for a city.',
input: z.object({ city: z.string() }),
output: z.object({ temp_c: z.number() }),
fulfil: ({ input, connectors }) => connectors.weather.current({ city: input.city }),
}),
]);Response-size limits
Every HTTP operation defaults to a 1 MiB decoded-response limit. Prefer a narrower endpoint, pagination, or a smaller upstream result before increasing it. When a representative response legitimately needs more, grant only that operation the required bytes (maximum 3 MiB):
search: {
type: 'read',
method: 'GET',
path: '/v1/search',
limits: { maxResponseBytes: 3 * 1024 * 1024 },
// input, output, and response mapping omitted
}The bound is inclusive and counts decoded streamed bytes, including compressed responses. Response mapping
runs after the body is read, so mapping fewer fields does not reduce transport buffering. An overflow stays
a connector_error with the safe structured reason response_too_large; response bodies, headers, URLs,
and credentials are never included.
Credentials
Reference secrets and non-secret config by name; the broker resolves them per tenant at call time.
secret("NAME")for credentials (API keys, tokens). Set withnoodle secrets set.variable("NAME")for non-secret config (a base URL, a tenant id). Set withnoodle variables set.
Supported auth kinds today are bearer, apiKey, and clientCredentials
(Shipped) — the latter defaults to the RFC-6749
client-credentials grant, with profile: "custom" for non-standard partner token endpoints. Per-user
delegated auth kinds are Roadmap (Phase 3).
Hosts are allowlisted
HTTP connectors are SSRF-guarded: set allowedOrigins to the exact hosts the connector may reach.
Requests to other hosts are rejected.
Expressions
An operation maps its request and response declaratively with ${...} expressions instead of imperative
code. The common scopes:
| Expression | Resolves to |
|---|---|
${input.x} | A field of the operation input. |
${response} | The raw HTTP response body (use ${response.field} for a field). |
${env.NAME} | A managed variable (variable("NAME")). |
operations: {
get_order: {
type: 'read',
method: 'GET',
path: '/orders/${input.id}', // interpolate input into the path
input: z.object({ id: z.string() }),
output: z.object({ total: z.number() }),
response: { total: '${response.amount}' }, // map response body to output
},
}Managed config (secret("NAME") / variable("NAME")) is set with noodle secrets set /
noodle variables set and resolved per tenant by the broker at call time. Per-user
${user.*} expressions are Roadmap (Phase 3).
Upstream MCP connectors
ShippedAdopt an existing remote Streamable HTTP MCP server with an explicit, reviewable snapshot:
noodle import mcp https://store.example/api/mcp \
--name store \
--output store-assistantThe command runs tools/list during import, validates and freezes each selected upstream tool name and
schema into generated TypeScript, and stores a secret-free drift snapshot. Runtime calls only those
declared operations. It never discovers or mirrors a changing live surface.
Upstream MCP annotations are hints, not a trust boundary. Every generated tool therefore starts as a
destructive confirmed action. Verify the actual upstream behavior, keep only the operations you intend to
publish, and deliberately change proven reads to read-only annotations. An upstream readOnlyHint never
silently removes Noodle's confirmation boundary.
For import-only authentication, read one header from an environment variable. Its value is kept in memory and never written:
export STORE_IMPORT_TOKEN=…
noodle import mcp https://store.example/api/mcp \
--name store \
--header-env Authorization=STORE_IMPORT_TOKENUse --check in CI to compare the live tool surface with the saved snapshot without changing source:
noodle import mcp https://store.example/api/mcp \
--output store-assistant \
--checkThe review classifies additions as additive, removals or contract changes as breaking, and description-only
changes as metadata-only. Every class still requires explicit author review; --check never accepts drift.
The generated .mcp({...}) connector is a backing contract. Publish ordinary intent-shaped tool(...)
capabilities with stable names, narrower inputs and outputs, annotations, confirmation, and optional React
views. One public tool can combine several MCP, HTTP, and compute operations. A Noodle view can add a widget
to an upstream tool that supplied no UI.
Curated, not transparent
Noodle does not forward upstream resources, prompts, annotations, _meta, widgets, or CSP. It does not
reuse an inbound client token upstream, and it does not act on upstream sampling, roots, or elicitation.
Each call uses one guarded session and a broker-minted credential, then closes the session.
Managed MCP endpoints use variable("NAME") plus an exact allowedOrigins entry. Static upstream auth can
use no auth, bearer, API key, or client credentials. Per-user delegated upstream OAuth is not part of this
initial connector contract.
Compute connectors
When a bit of logic is genuinely needed (reshaping a response, deriving a value), a compute connector runs self-contained TypeScript in a sandbox with no ambient authority: no network, filesystem, secrets, or tokens.
const convert = connector('convert')
.version('1.0.0')
.compute('c_to_f', {
type: 'read',
input: z.object({ temp_c: z.number() }),
output: z.object({ temp_f: z.number() }),
run: (input) => ({ temp_f: input.temp_c * 1.8 + 32 }),
});The run function must be self-contained: no closures over outer variables, no imports, no fetch.
It reaches other operations only through an allowlisted host.callOperation, and you can bound it
with limits (timeout, memory, output size). Bind it on use and call it like any connector.
Binding connectors
useexposes a connector to your tools, callable asctx.connectors.<name>.<operation>().providesregisters a connector in the catalog for compute steps to reach viahost.callOperation, without exposing it directly to tools.
Import paths
noodle import mcp generates a governed frozen MCP connector. noodle import openapi generates HTTP
connector starter code from an OpenAPI document. Review either generated surface before publishing tools.