Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 23 additions & 7 deletions .agents/skills/naftiko-capability/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ 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` and `consumes`
- **Capability** — root technical config; contains `exposes`, `consumes`, and `aggregates`
- **Consumes** — HTTP client adapter: baseUri, namespace, resources, operations
- **Exposes** — server adapter: REST (`type: rest`), MCP (`type: mcp`), or Skill (`type: skill`)
- **Aggregates** — DDD-inspired domain building blocks; each aggregate groups reusable functions under a namespace. Tools and operations reference functions via `ref`
- **Binds** — variable injection from file (dev) or runtime (prod)
- **Namespace** — unique identifier linking exposes to consumes via routing

Expand All @@ -50,6 +51,7 @@ for *how*.
| "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 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 |
Expand Down Expand Up @@ -110,11 +112,15 @@ Specification directly.
8. `variable` expressions resolve from `binds` keys.
9. `ForwardConfig` requires `targetNamespace` (single string, not array)
and `trustedHeaders` (at least one entry).
10. MCP tools must have `name` and `description`. MCP tool input parameters
must have `name`, `type`, and `description`.
11. ExposedOperation supports exactly two modes (oneOf): simple (`call` +
optional `with`) or orchestrated (`steps` + optional `mappings`). Never
mix fields from both modes.
10. MCP tools must have `name` and `description` (unless using `ref`, in which
case they are inherited from the referenced aggregate function). MCP tool input
parameters must have `name`, `type`, and `description`. Tools may declare optional
`hints` (readOnly, destructive, idempotent, openWorld) — these map to
MCP `ToolAnnotations` on the wire.
11. ExposedOperation supports three modes (oneOf): simple (`call` +
optional `with`), orchestrated (`steps` + optional `mappings`), or
ref (`ref` to an aggregate function). Never mix fields from
incompatible modes.
12. Do not modify `scripts/lint-capability.sh` unless explicitly asked —
it wraps Spectral with the correct ruleset and flags.
13. Do not add properties that are not in the JSON Schema — the schema
Expand All @@ -127,4 +133,14 @@ Specification directly.
outputs are dead declarations that add noise without value.
16. 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.
Redundant prefixes reduce readability without adding disambiguation.
17. When using `ref` on MCP tools or REST operations, the `ref` value must
follow the format `{aggregate-namespace}.{function-name}` and resolve
to an existing function in the capability's `aggregates` array.
18. Do not chain `ref` through multiple levels of aggregates — `ref`
resolves to a function in a single aggregate, not transitively.
19. Aggregate functions can declare `semantics` (safe, idempotent, cacheable).
When exposed via MCP, the engine auto-derives `hints` from semantics.
Explicit `hints` on the MCP tool override derived values.
20. Do not duplicate a full function definition inline on both MCP tools
and REST operations — use `aggregates` + `ref` instead.
32 changes: 32 additions & 0 deletions .agents/skills/naftiko-capability/references/design-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ Avoid:
- Use tools for actions and resources for read-only data access.
- Prefer small tools with crisp, typed `inputParameters`.
- If an MCP tool becomes complex, switch to orchestration and document it clearly.
- Use `hints` to signal tool behavior to clients:
- Set `readOnly: true` for tools that only read data (GET-like).
- Set `destructive: true` for tools that delete or overwrite (DELETE, PUT).
- Set `idempotent: true` for tools safe to retry.
- Set `openWorld: true` for tools calling external APIs; `false` for closed-domain tools (local data, caches).

## Orchestration guidelines (steps + mappings)

Expand Down Expand Up @@ -128,6 +133,33 @@ Do not mix fields from both modes in one operation/tool.
- Do not expose internal IDs unless they are necessary and meaningful for consumers.
- Avoid returning massive nested objects if only a few fields are needed.

## Aggregate design guidelines (DDD-inspired)

Aggregates borrow from [Domain-Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks): each aggregate groups related functions under a namespace that represents a single domain concept (the **Aggregate Root**). Functions within the aggregate are the operations that agents and clients can invoke.

### Define aggregate boundaries around domain concepts

- One aggregate = one domain concept (e.g., `forecast`, `ticket`, `user-profile`).
- Functions within an aggregate should operate on the same domain data — if a function feels unrelated, it likely belongs in a different aggregate.
- Keep function names intention-revealing and adapter-neutral: `get-forecast`, not `mcp-get-forecast` or `rest-forecast-query`.

### Use `ref` to share functions across adapters

- When the same domain operation is exposed via REST *and* MCP, define it once in `aggregates` and reference it with `ref` from both adapters.
- Override only the adapter-specific fields at the tool/operation level (e.g., `method` for REST, `hints` for MCP).
- Do not duplicate the full function definition inline when `ref` can carry it.

### Use `semantics` as the single source of behavioral truth

- Declare `safe`, `idempotent`, and `cacheable` on the aggregate function — they describe the domain behavior, not a transport detail.
- Let the engine derive MCP `hints` from semantics automatically. Override hints only when the derived values are insufficient (e.g., setting `openWorld`).
- Do not set `semantics` on functions that are only exposed via REST — REST has its own semantic model via HTTP methods.

### Keep aggregates lean

- Start with functions only (the "functions-first" approach). Entities, events, and other DDD stereotypes may be added in future schema versions.
- Avoid creating an aggregate for a single function that is only used in one place — aggregates pay off when sharing across adapters or when grouping related operations.

## Secret management (dev → prod)

- Use `binds` for any sensitive values or environment-dependent configuration.
Expand Down
25 changes: 22 additions & 3 deletions .agents/skills/naftiko-capability/references/wrap-api-as-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ When wrapping an API as MCP, design in this order:
- simple mode: inline typed output parameters (`MappedOutputParameter`)
- orchestrated mode: named typed outputs (`OrchestratedOutputParameter`) plus `mappings`

4) If the same operation is also exposed via REST, consider using **aggregates**:

- Define the function once in `capability.aggregates[]` (DDD Aggregate pattern)
- Reference it from MCP tools with `ref: {namespace}.{function-name}`
- Declare `semantics` (safe, idempotent, cacheable) on the function — the engine auto-derives MCP `hints`
- Override only MCP-specific fields on the tool (e.g., explicit `hints` for `openWorld`)
- `name` and `description` are inherited from the function unless overridden

## Constraints (aligned with schema + rules)

### Global constraints
Expand All @@ -95,16 +103,27 @@ When wrapping an API as MCP, design in this order:
For each MCP tool:

1. `name` (kebab-case / IdentifierKebab) is required and must be stable (used as the MCP tool name).
When using `ref`, `name` is optional — inherited from the aggregate function.
2. `description` is required (agent discovery depends on it).
3. If tool is simple:
When using `ref`, `description` is optional — inherited from the aggregate function.
3. `hints` is optional — declares behavioral hints mapped to MCP `ToolAnnotations`:
- `readOnly` (bool) — tool does not modify its environment (default: false)
- `destructive` (bool) — tool may perform destructive updates (default: true, meaningful only when readOnly is false)
- `idempotent` (bool) — repeating the call has no additional effect (default: false, meaningful only when readOnly is false)
- `openWorld` (bool) — tool interacts with external entities (default: true)
When using `ref` with `semantics` on the function, hints are auto-derived (safe→readOnly/destructive, idempotent→idempotent). Explicit hints override derived values.
4. If tool is simple:
- must define `call: {namespace}.{operationName}`
- may define `with`
- should define `outputParameters` (typed) when you want structured results.
4. If tool is orchestrated:
5. If tool is orchestrated:
- must define `steps` (min 1), each step has `name`
- may define `mappings`
- `outputParameters` must use orchestrated output parameter objects (named + typed)
5. Tool `inputParameters`:
6. If tool uses `ref`:
- must define `ref: {namespace}.{function-name}` pointing to an aggregate function
- all other fields are optional — inherited from the function, explicit values override
7. Tool `inputParameters`:
- each parameter must have `name`, `type`, `description`
- set `required: false` explicitly for optional params (default is true)

Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ When designing or modifying a Capability:
- Keep the [Naftiko Specification](src/main/resources/schemas/naftiko-schema.json) and the [Naftiko Rules](src/main/resources/rules/naftiko-rules.yml) as first-class citizens — the schema enforces structure, the rules enforce cross-object consistency, quality, and security
- Look at `src/main/resources/schemas/examples/` for patterns before writing new capabilities
- When renaming a consumed field for a lookup `match`, also add a `ConsumedOutputParameter` on the consumed operation to map the raw field name to a kebab-case name — otherwise the lookup has nothing to match against
- Use `aggregates` to define reusable domain functions when the same operation is exposed through multiple adapters (REST and MCP) — this follows the DDD Aggregate pattern: one definition, multiple projections
- Declare `semantics` (safe, idempotent, cacheable) on aggregate functions to describe domain behavior — the engine derives MCP `hints` automatically
- Override only adapter-specific fields when using `ref` (e.g., `method` for REST, `hints` for MCP) — let the rest be inherited from the function

**Don't:**
- Expose an `inputParameter` that is not used in any step
Expand All @@ -103,6 +106,8 @@ When designing or modifying a Capability:
- Use `MappedOutputParameter` (with `mapping`, no `name`) when the tool/operation uses `steps` — use `OrchestratedOutputParameter` (with `name`, no `mapping`) instead
- Use typed objects for lookup step `outputParameters` — they are plain string arrays of field names to extract (e.g. `- "fullName"`)
- Put a `path` property on an `ExposedOperation` — extract multi-step operations with a different path into their own `ExposedResource`
- Duplicate a full function definition inline on both MCP tools and REST operations — use `aggregates` + `ref` instead
- Chain `ref` through multiple levels of aggregates — `ref` resolves to a function in a single aggregate, not transitively

## Contribution Workflow

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Each capability is a coarse piece of domain that consumes existing HTTP-based AP
| Data Format Conversion | Transform **Protobuf**, **XML**, **YAML**, **CSV**, **TSV**, **PSV**, **Avro**, **HTML**, and **Markdown** payloads into JSON |
| HTTP API Consumption | Connect to any HTTP-based API with built-in authentication support |
| Templating & Querying | Use **Mustache** templates and **JSONPath** expressions for flexible data mapping |
| Domain-Driven Aggregates | Define reusable domain functions once, expose via multiple adapters — inspired by **DDD** Aggregate pattern |
| AI Native | Designed for Context Engineering and Agent Orchestration, making capabilities directly consumable by AI agents |
| Docker Native | Ships as a ready-to-run **Docker** container |
| Extensible | Open-source core extensible with new protocols and adapters |
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/io/naftiko/Capability.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.naftiko.engine.AggregateRefResolver;
import io.naftiko.engine.BindingResolver;
import io.naftiko.engine.ConsumesImportResolver;
import io.naftiko.spec.ExecutionContext;
Expand Down Expand Up @@ -68,6 +69,10 @@ public Capability(NaftikoSpec spec, String capabilityDir) throws Exception {
importResolver.resolveImports(spec.getCapability().getConsumes(), capabilityDir);
}

// Resolve aggregate function refs before adapter initialization
AggregateRefResolver aggregateRefResolver = new AggregateRefResolver();
aggregateRefResolver.resolve(spec);

// Resolve bindings early for injection into adapters
BindingResolver bindingResolver = new BindingResolver();
ExecutionContext context = new ExecutionContext() {
Expand Down
Loading
Loading