Overview
The Naftiko Framework lets you declare capabilities — functional units that consume external APIs and expose adapters (REST, MCP, Skill). A capability is a single YAML file validated against the Naftiko JSON Schema (v1.0.0-alpha1).
Key spec objects you will work with:
- Info — metadata: label, description, tags, stakeholders
- Capability — root technical config; contains
exposes,consumes, andaggregates - Consumes — HTTP client adapter: baseUri, namespace, resources, operations
- Exposes — server adapter: REST (
type: rest), MCP (type: mcp), Skill (type: skill), or Control (type: control) - Aggregates — DDD-inspired domain building blocks; each aggregate groups reusable functions under a namespace. Tools and operations reference functions via
ref - Observability — optional OTel configuration on the control adapter: trace sampling, propagation format, OTLP exporter endpoint
- Script Steps — embed JavaScript, Python, or Groovy transformations between API calls using sandboxed GraalVM engines. Scripts read step results via bound variables and produce output through a
resultvariable - Binds — variable injection from file (dev) or runtime (prod)
- Namespace — unique identifier linking exposes to consumes via routing
Canonical sources (read these, never duplicate them):
- Specification:
src/main/resources/wiki/Specification.md - JSON Schema:
src/main/resources/schemas/capability-schema.json - Spectral Ruleset:
src/main/resources/schemas/naftiko-rules.yml
Decision Framework
Match the user's situation to a story reference. Each story explains why (the user's problem), what (the Naftiko pattern), and points to the spec for how.
| Situation | Action |
|---|---|
| "I want to combine several APIs into a single reusable service" | Read references/reusable-capability.md |
| "I want to expose this API as an MCP server, including tools, resources, and prompts" | Read references/wrap-api-as-mcp.md |
| "I want to proxy an API today and encapsulate it incrementally" | Read references/proxy-then-customize.md |
| "I want to chain multiple HTTP calls to consumed APIs and expose the result into a single REST operation" | Read references/chain-api-calls.md |
| "I need to go from local test credentials to production secrets" | Read references/dev-to-production.md |
| "I want to define a domain function once and expose it via both REST and MCP" | Use aggregates with ref — read references/design-guidelines.md (Aggregate Design Guidelines) |
| "I want to add health checks, Prometheus metrics, or trace inspection" | Read references/control-port-observability.md |
| "I want to enable OpenTelemetry distributed tracing and RED metrics" | Read references/control-port-observability.md |
| "I want to transform data between API calls using JavaScript, Python, or Groovy" | Read references/inline-script-step.md |
| "I want to prototype a tool or endpoint before the backend exists" or "I want to return static or dynamic mock data" | Read references/mock-capability.md |
| "I want to build a full-featured capability that does all of the above" | Read all stories in order, then use assets/capability-example.yml as structural reference |
| "I have a YAML validation error" | Run scripts/lint-capability.sh — see Lint workflow below |
| "I'm done writing — what should I check before shipping?" | Read references/design-guidelines.md, then run lint |
If the user's intent does not match any story, read the canonical Specification directly.
Workflows
Edit a Capability
- Analyze and propose. Infer the story from context (user prompt, open files, existing capabilities). Read that story file, then present a capability outline for the user to validate. Only ask what you cannot infer.
- Scaffold. Copy
assets/capability-template.yml. The document must begin withnaftiko: "1.0.0-alpha1". - Fill exposes. Choose the adapter type (REST, MCP, or Skill) and
follow the pattern from the story. For REST operations, use
call+with(simple) orsteps+mappings(orchestrated) — never both. - Fill consumes. For each external API: set
type: "http", a uniquenamespace(kebab-case),baseUri(no trailing slash),resources, andoperations. Addauthenticationif needed. - Add
bindsif secrets or environment variables are needed. Uselocation: "file:"for dev; remove it for prod. - Review. Read
references/design-guidelines.mdand check your document against the design guidelines. - Validate. Run
bash scripts/lint-capability.sh path/to/capability.yml. Fix any errors, then re-lint until clean.
Lint a Capability
- Run the lint script: bash scripts/lint-capability.sh path/to/capability.yml Do NOT regenerate or modify this script.
- Spectral reports errors and warnings with rule names. Common rules:
naftiko-namespaces-unique(error) — duplicate namespacenaftiko-consumes-baseuri-no-trailing-slash(warn) — trailing/naftiko-consumed-resource-no-query-in-path(warn) — query in pathnaftiko-rest-resource-path-no-trailing-slash(warn)naftiko-baseuri-not-example(warn) — placeholder URInaftiko-no-script-tags-in-markdown(error) — XSS in descriptionsnaftiko-consumes-description(warn) — missing descriptionnaftiko-control-port-singleton-and-unique(error) — more than one control adapter, or port collisionnaftiko-control-address-localhost-warning(warn) — control port bound to non-localhost For the full rule list, read the Spectral ruleset file directly.
- Fix and re-lint. Repeat until clean.
Mock a Capability
Use mock mode when the upstream API does not exist yet, or you want to
prototype a contract-first design. Read references/mock-capability.md
before writing any mock output parameters.
Key rules:
- Omit
call,steps, and the entireconsumesblock — they are not needed for a pure mock capability. - Use
valueonMappedOutputParameterfor static strings (value: "Hello") or Mustache templates (value: "Hello, {{name}}!"). Do NOT useconst— it is schema-only and is never used at runtime. - Mustache placeholders in
valueare resolved against the tool's or endpoint's input parameters by name. Only top-level input parameter names are in scope — no nesting, nowithremapping. valueandmappingare mutually exclusive on a scalar output parameter — never set both.- Object and array type output parameters in mock mode must carry
valueon each leaf scalar descendant; the container itself has novalue. - When the mock is ready to be wired to a real API, add
consumes, replacevaluewithmapping, and addcallorsteps— the exposed contract does not change. andtrustedHeaders(at least one entry). - MCP tools must have
nameanddescription(unless usingref, in which case they are inherited from the referenced aggregate function). MCP tool input parameters must havename,type, anddescription. Tools may declare optionalhints(readOnly, destructive, idempotent, openWorld) — these map to MCPToolAnnotationson the wire. - ExposedOperation supports three modes (oneOf): simple (
call+ optionalwith), orchestrated (steps+ optionalmappings), or ref (refto an aggregate function). Never mix fields from incompatible modes. - Do not modify
scripts/lint-capability.shunless explicitly asked — it wraps Spectral with the correct ruleset and flags. - Do not add properties that are not in the JSON Schema — the schema
uses
additionalProperties: falseon most objects. - Every exposed
inputParametermust be referenced in at least one step'swithblock or mapping — orphan parameters bloat the API surface and confuse consumers. - Every consumed
outputParametermust be referenced in the exposed part (via mappings, oroutputParameters) — unreferenced outputs are dead declarations that add noise without value. - Do not prefix variable names with the capability, namespace, or resource name — variables are already scoped to their context. Redundant prefixes reduce readability without adding disambiguation.
- When using
refon MCP tools or REST operations, therefvalue must follow the format{aggregate-namespace}.{function-name}and resolve to an existing function in the capability'saggregatesarray. - Do not chain
refthrough multiple levels of aggregates —refresolves to a function in a single aggregate, not transitively. - Aggregate functions can declare
semantics(safe, idempotent, cacheable). When exposed via MCP, the engine auto-deriveshintsfrom semantics. Explicithintson the MCP tool override derived values. - Do not duplicate a full function definition inline on both MCP tools
and REST operations — use
aggregates+refinstead. - In mock mode, use
valueon output parameters — neverconst.constis a JSON Schema keyword retained for validation and linting only; it has no effect at runtime. - In mock mode, Mustache templates in
valuefields resolve only against top-level input parameter names. Do not referencewith-remapped consumed parameter names — those are not in scope for output resolution. - At most one
type: "control"adapter is allowed per capability. Its port must not collide with any business adapter port. - The control port
addressshould belocalhostor127.0.0.1for security. Binding to0.0.0.0exposes management endpoints externally. - The
/metricsand/tracescontrol port endpoints are configured underobservability.metrics.localandobservability.traces.localrespectively. Observability is enabled by default — allenabledfields default totrue. Setenabled: falseto disable specific endpoints or observability entirely. They return 503 when the OTel SDK is not on the classpath. - When adding a control port, also add
observabilityon the control adapter if you want metrics and traces to produce data. Without it, the local endpoints return empty or 503 responses. - The
observability.exporters.otlp.endpointfield supports Mustache expressions for binds (e.g."{{OTEL_ENDPOINT}}"). Use binds to keep exporter URLs environment-specific. - Script steps require a
fileproperty.languageandlocationare optional when Control Port defaults are configured viamanagement.scripting.defaultLanguageandmanagement.scripting.defaultLocation. - Script
dependenciesare pre-evaluated in order before the main script. Shared logic (helpers, constants) goes in dependencies. - The script must assign to the
resultvariable to produce output. Previous step results are bound as variables matching step names. - When
management.scriptingis configured on the Control Port,defaultLanguageanddefaultLocationserve as fallbacks — step-level values take precedence. allowedLanguagesrestricts which languages script steps may use. If omitted, all three languages (javascript,python,groovy) are allowed.