Skip to content
Merged
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
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ DICE (Domain-Integrated Context Engineering) is a proposition-first knowledge su

| Module | What it owns |
|---|---|
| `dice` | The entire domain: `Proposition` model, `PropositionStore`/`PropositionRepository` SPIs, extraction pipeline, revision/conflict detection, entity resolution, projectors (graph, Prolog, memory), graph and discovery query/retrieval, incremental analysis, in-memory and file-backed stores, tuProlog integration, REST endpoints |
| `dice` | The entire domain: `Proposition` model, `PropositionStore`/`PropositionRepository` SPIs, extraction pipeline, revision/conflict detection, entity resolution, projectors (graph, Prolog, memory), graph and discovery query/retrieval, incremental analysis, in-memory and file-backed stores, tuProlog integration, REST endpoints, MCP tool surface (`DiceMcpTools`) |
| `dice-storage` | Drivine/Neo4j implementation of `PropositionRepository`, `ChunkHistoryStore`, and `DecayManager`; uses Kotlin 2.2 for the Drivine KSP-generated query DSL |
| `dice-storage-autoconfigure` | Spring Boot auto-configuration that wires the right backend based on `embabel.dice.store.type`, schedules the decay tick, and provides auto-configuration for the multi-signal duplicate collector (properties prefix `embabel.dice.collector`) |
| `dice-report` | Output projectors over propositions: rationale (why a fact is believed, with evidence), structured report, and surprising-link discovery |
| `dice-ingestion` | Ingestion SPI (artifacts → chunks) with a content-hash dedup ledger so the same source isn't extracted twice |
| `dice-metamodel` | Schema versioning: `MetamodelVersion` content-hash stamps over the governed types of a `DataDictionary`, `GovernedTypeSelector`, the `DeclaredSchemaSource` opt-in, and the `MetamodelVersionStore` contract. Pure JVM, with no dependency on `dice` |
| `dice-integration-tests` | Test-only: the cross-feature end-to-end canonical-flow harness |
| `dice-mcp-autoconfigure` | Spring Boot auto-configuration that exports `DiceMcpTools` over MCP via embabel-agent when `embabel.dice.mcp.enabled=true` |

## Build & test

Expand Down Expand Up @@ -66,6 +67,7 @@ The `dice` module is organized by responsibility:
| `com.embabel.dice.provenance` | `ProvenanceEntry`, `SourceLocator` — rich evidence links from propositions back to source material |
| `com.embabel.dice.query.oracle` | `Oracle`, `LlmOracle`, `PrologTools` — natural language question answering over propositions |
| `com.embabel.dice.web.rest` | Optional REST endpoints for the pipeline and memory; activated by `spring-webmvc` on the classpath |
| `com.embabel.dice.mcp` | `DiceMcpTools` — simplified MCP tool surface (`dice_recall`, `dice_list`, `dice_store`, `dice_get`); `contextId` on every call is a scope, not a credential |

## Conventions

Expand All @@ -85,6 +87,7 @@ The `dice` module is organized by responsibility:

- **Adding or changing extraction logic** → `com.embabel.dice.proposition.extraction.LlmPropositionExtractor` and the Mustache prompt templates in `dice/src/main/resources/dice/`.
- **Wiring a new Spring Boot app** → `dice-storage-autoconfigure`, specifically `DiceStorageAutoConfiguration` (backend selection) and `DiceStoreProperties` (property keys). Set `embabel.dice.store.type=graph` for Neo4j.
- **Exposing DICE over MCP** → `DiceMcpTools` in `com.embabel.dice.mcp` for the tool surface; `dice-mcp-autoconfigure` + `embabel-agent-starter-mcpserver` for zero-code export (`embabel.dice.mcp.enabled=true`).
- **Understanding the proposition data model** → `Proposition.kt` in `com.embabel.dice.proposition`. Every field is documented inline.
- **Adding a new entity resolver strategy** → implement `CandidateSearcher` in `com.embabel.dice.common.resolver.searcher`, then compose it into an `EscalatingEntityResolver`.
- **Writing integration tests against Neo4j** → look at `DrivinePropositionStoreIntegrationTest` in `dice-storage/src/test`; it shows the `@SpringBootTest` + Testcontainers pattern in use.
Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2414,6 +2414,54 @@ Everything is pushed into the database rather than scanned in memory:
> `dice-storage/HANDOFF.md` for architecture and `dice-storage/INTEGRATE-INTO-ASSISTANT.md` for a
> migration walkthrough.

### MCP Server

Expose DICE recall/list/store/get to an MCP client (Claude Desktop, Cursor, etc.) with
`dice-mcp-autoconfigure` and embabel-agent's MCP server starter. Off until you set
`embabel.dice.mcp.enabled=true`. Every tool takes a `contextId` (the Kotlin parameter name
`KotlinMethodTool` publishes). That is a scope, not a credential. It keeps one call from
reading another context; authorization is the host MCP server's job. In-process `Memory` /
`DiscoveryTools` bake context in at construction instead. `dice_store` is off until you set
`embabel.dice.mcp.writes-enabled=true`.

```xml
<dependency>
<groupId>com.embabel.dice</groupId>
<artifactId>dice-mcp-autoconfigure</artifactId>
<version>${dice.version}</version>
</dependency>
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter-mcpserver</artifactId>
<version>${embabel-agent.version}</version>
</dependency>
```

```yaml
embabel:
dice:
mcp:
enabled: true
```

| Tool | Description |
|------|-------------|
| `dice_recall` | Hybrid semantic + keyword search in a `contextId` |
| `dice_list` | List active propositions for a context |
| `dice_store` | Store a proposition directly (off unless `writes-enabled=true`) |
| `dice_get` | Fetch one proposition by `propositionId`; includes status so a stale fact does not look active |

`dice_recall` and `dice_list` share one result format, each line carrying the `id=` that
`dice_get` takes, so a client can search and then drill into a single fact. Their `limit` is
clamped to 100.

`dice_store` is omitted from the export unless `embabel.dice.mcp.writes-enabled=true`. When
it is on, it writes a fact with empty mentions and no provenance, so it is retrievable by
vector and keyword only, not by entity expansion or graph projection. Use the ingestion
pipeline when the fact needs to be wired into the rest of the knowledge flow.

Discovery and graph tools stay on `DiscoveryTools.asTools(...)` / `GraphQueryTools.asTools(...)`.

### API Key Security

DICE provides API key authentication for the REST endpoints. Enable it via configuration:
Expand Down
49 changes: 49 additions & 0 deletions dice-mcp-autoconfigure/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# `dice-mcp-autoconfigure` module — Agent Navigation Guide

Spring Boot wiring that exports [DiceMcpTools](../dice/src/main/kotlin/com/embabel/dice/mcp/DiceMcpTools.kt)
over embabel-agent's MCP server. No domain logic — just an `@AutoConfiguration` that assembles beans
from `dice`. `contextId` on every tool is a caller-supplied scope, not a credential. The
check lives on `DiceMcpTools` itself; authorization is the host MCP server's job.

## What's here

- **`DiceMcpAutoConfiguration`** — `DiceMcpTools` + named `diceMcpToolExport` (`McpToolExport`).
Opt-in via `embabel.dice.mcp.enabled=true`. Requires `McpToolExport` on the classpath and a
`PropositionRepository` bean. `afterName` waits for `DiceStorageAutoConfiguration` when that
module is present so the store bean exists before `@ConditionalOnBean` is asked.
- **`DiceMcpProperties`** — `embabel.dice.mcp`: `enabled` (default false), `min-confidence`
(default 0.5), `default-limit` (default 10), `writes-enabled` (default false).

## Property reference

| Property | Default | Meaning |
|---|---|---|
| `embabel.dice.mcp.enabled` | `false` | Master switch. Off means no beans. |
| `embabel.dice.mcp.min-confidence` | `0.5` | Minimum effective confidence for recall/list |
| `embabel.dice.mcp.default-limit` | `10` | Default result cap for recall/list. Must be `1..100` |
| `embabel.dice.mcp.writes-enabled` | `false` | When true, export also includes `dice_store` |

Every collaborator is `@ConditionalOnMissingBean`, so an app's own `DiceMcpTools` or
`diceMcpToolExport` bean wins.

## Dependencies

- `dice` — `DiceMcpTools` and the proposition store SPI.
- `embabel-agent-mcpserver` (optional) — `McpToolExport`. The host also adds
`embabel-agent-starter-mcpserver`.
- `embabel-agent-api` (provided) — supplied at runtime by the consuming application.

## Gotchas

- MCP export is **opt-in**. Unlike the collector (`enabled` default true), this stays dark until
`embabel.dice.mcp.enabled=true`. `dice_store` is a second switch (`writes-enabled`, default
false) because a direct write skips extraction, admission, and provenance.
- Without a `PropositionRepository` bean the auto-config class may load but it exports nothing.
- `default-limit` is bounded by `DiceMcpTools.MAX_LIMIT` (100), the ceiling the tools clamp every
caller-supplied `limit` to. A larger default would bind and then be silently truncated on every
call, so it fails startup instead.
- `afterName` is a string, not `after = [DiceStorageAutoConfiguration::class]`, so this module
has no compile dependency on `dice-storage-autoconfigure`. The combined wiring test (test-scope
only) is what proves the name is right and the store bean is visible.
- Discovery and graph tools are not on this path. They bake context in at construction; use
`DiscoveryTools.asTools(...)` / `GraphQueryTools.asTools(...)` for in-process agents.
100 changes: 100 additions & 0 deletions dice-mcp-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.embabel.dice</groupId>
<artifactId>dice-parent</artifactId>
<version>0.2.0-SNAPSHOT</version>
</parent>
<artifactId>dice-mcp-autoconfigure</artifactId>
<packaging>jar</packaging>
<name>Dice MCP Autoconfigure</name>
<description>Spring Boot auto-configuration that exports DICE tools over MCP via embabel-agent</description>

<dependencies>
<dependency>
<groupId>com.embabel.dice</groupId>
<artifactId>dice</artifactId>
</dependency>

<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-mcpserver</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-rag-core</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<!--
Test-only: prove afterName waits for DiceStorageAutoConfiguration.
Production stays free of that compile dependency — afterName is a string
for that reason (same lesson as #102).
-->
<dependency>
<groupId>com.embabel.dice</groupId>
<artifactId>dice-storage-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito.kotlin</groupId>
<artifactId>mockito-kotlin</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
<arg>-Xjvm-default=all</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2024-2026 Embabel Pty Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.embabel.dice.mcp.autoconfigure

import com.embabel.agent.api.tool.ToolObject
import com.embabel.agent.mcpserver.McpToolExport
import com.embabel.dice.mcp.DiceMcpTools
import com.embabel.dice.proposition.PropositionRepository
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean

/**
* Registers [DiceMcpTools] and exports them as MCP tools when embabel-agent's MCP server is present.
*
* Typical application dependencies:
* ```xml
* <dependency>
* <groupId>com.embabel.dice</groupId>
* <artifactId>dice-mcp-autoconfigure</artifactId>
* </dependency>
* <dependency>
* <groupId>com.embabel.agent</groupId>
* <artifactId>embabel-agent-starter-mcpserver</artifactId>
* </dependency>
* ```
*
* ```yaml
* embabel:
* dice:
* mcp:
* enabled: true
* writes-enabled: true # optional; dice_store stays off otherwise
* ```
*
* `afterName` waits for `dice-storage-autoconfigure` when that module is on the classpath, so
* `@ConditionalOnBean(PropositionRepository)` sees the store bean. If storage autoconfig is absent,
* the named class is ignored and the host supplies its own repository.
*/
@AutoConfiguration(afterName = ["com.embabel.dice.storage.autoconfigure.DiceStorageAutoConfiguration"])
@ConditionalOnClass(McpToolExport::class)
@ConditionalOnProperty(prefix = "embabel.dice.mcp", name = ["enabled"], havingValue = "true")
@EnableConfigurationProperties(DiceMcpProperties::class)
class DiceMcpAutoConfiguration {

private val logger = LoggerFactory.getLogger(DiceMcpAutoConfiguration::class.java)

@Bean
@ConditionalOnBean(PropositionRepository::class)
@ConditionalOnMissingBean(DiceMcpTools::class)
fun diceMcpTools(
repository: PropositionRepository,
properties: DiceMcpProperties,
): DiceMcpTools = DiceMcpTools(
repository = repository,
minConfidence = properties.minConfidence,
defaultLimit = properties.defaultLimit,
)

@Bean("diceMcpToolExport")
@ConditionalOnBean(DiceMcpTools::class)
@ConditionalOnMissingBean(name = ["diceMcpToolExport"])
fun diceMcpToolExport(tools: DiceMcpTools, properties: DiceMcpProperties): McpToolExport {
val exported = if (properties.writesEnabled) {
ToolObject(objects = listOf(tools))
} else {
ToolObject(objects = listOf(tools)).withFilter { it != DiceMcpTools.STORE }
}
val names = if (properties.writesEnabled) DiceMcpTools.TOOL_NAMES else DiceMcpTools.READ_TOOL_NAMES
logger.info("Exporting DICE MCP tools: {}", names.sorted())
return McpToolExport.fromToolObject(exported)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2024-2026 Embabel Pty Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.embabel.dice.mcp.autoconfigure

import com.embabel.dice.mcp.DiceMcpTools
import org.springframework.boot.context.properties.ConfigurationProperties

/**
* Configuration for exporting DICE as MCP tools.
*
* Requires `embabel-agent-starter-mcpserver` (or `embabel-agent-mcpserver`) on the classpath
* and `embabel.dice.mcp.enabled=true`.
*/
@ConfigurationProperties(prefix = "embabel.dice.mcp")
data class DiceMcpProperties(
/** Master switch. Default false so MCP export is opt-in. */
val enabled: Boolean = false,
/** Minimum effective confidence for recall/list tools (0.0–1.0). */
val minConfidence: Double = 0.5,
/**
* Default result limit for recall/list tools. Bounded by [DiceMcpTools.MAX_LIMIT]: a larger
* value would be silently clamped at call time, so it fails startup instead.
*/
val defaultLimit: Int = 10,
/**
* When false (the default), `dice_store` is omitted from the export. Store writes an ACTIVE
* proposition without extraction, admission, or provenance; that is a different capability
* from recall and is independently opt-in.
*/
val writesEnabled: Boolean = false,
) {
init {
require(minConfidence in 0.0..1.0) { "embabel.dice.mcp.min-confidence must be between 0.0 and 1.0" }
require(defaultLimit in 1..DiceMcpTools.MAX_LIMIT) {
"embabel.dice.mcp.default-limit must be between 1 and ${DiceMcpTools.MAX_LIMIT}"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.embabel.dice.mcp.autoconfigure.DiceMcpAutoConfiguration
Loading
Loading