feat(mcp): expose DICE tools for MCP server integration - #52
Conversation
993700f to
31f9763
Compare
31f9763 to
95f027f
Compare
|
Rebased onto latest main (through #60). Fixed the parent version to 0.1.1-SNAPSHOT. Build and DCO are green again. Ready when you are. |
95f027f to
2b40686
Compare
|
@LordKay-sudo - how are you? DICE is undergoing refactoring; could you please try to rebase? Thank you |
2b40686 to
b962c1d
Compare
|
Hallo, I'm well thanks. Rebased onto current main and checked it against 0.2 / agent 1.5. Same scope: context-scoped DiceMcpTools and opt-in autoconfig. Cut extract/assert and the core/extended profile so this stays four tools. Discovery/graph tools still out of this PR. |
|
From CODEX:
|
igordayen
left a comment
There was a problem hiding this comment.
@LordKay-sudo - few comments from my side and codex. Please also be aware of PR
embabel/embabel-agent#1833 on tool name uniqueness.
Please add comments to this PR, if you see better approaches, rather than Agent_Tool.
Thank you
| * | ||
| * In-process [com.embabel.dice.agent.Memory] and [com.embabel.dice.agent.DiscoveryTools] bake | ||
| * [ContextId] in at construction, so an agent cannot name another tenant. MCP clients are | ||
| * stateless and may serve many sessions, so every tool takes an explicit `context_id`. That is |
There was a problem hiding this comment.
I don't think stateless MCP requires the model to choose a context. With this shape, tenant selection becomes model-generated input and every host has to build authorization around it. Could we expose a host-supplied context policy, with a fixed-context implementation for the common case and an authorizing resolver for multi-tenant servers? Then the tool operates on a resolved ContextId, and caller-supplied context remains an explicit policy choice.
There was a problem hiding this comment.
Agreed. A model-chosen tenant should not be the default. A host policy (fixed context for the common case, an authorizing resolver when the host is multi-tenant) is the right shape. The tools then run on a resolved ContextId.
Do you want that policy in this PR, or should I land the schema, sanitizer, and write-switch fixes first and do the policy next?
There was a problem hiding this comment.
Do you want that policy in this PR, or should I land the schema, sanitizer, and write-switch fixes first and do the policy next?
@LordKay-sudo - please assess the number of additional artifacts involved. This PR is already medium-size, thank you
There was a problem hiding this comment.
@igordayen Counted it. A host context policy is a new SPI, two implementations, autoconfig properties, schema changes on all four tools, a new test class plus rewrites of the isolation tests and the MCP docs. Callers currently have to pass contextId; a fixed-context host would take that off the model. That is a second public contract in this PR.
I would land this one as is and do the policy next.
There was a problem hiding this comment.
Thanks @LordKay-sudo, that’s fair. Let’s keep this scoped and follow up on context policy once we have a concrete client integration to drive it. My concern about model-selected context still stands, but I don’t think we need to design the full abstraction here.
|
@igordayen I would keep the
|
|
@igordayen The IAE hole is real. I will validate inputs outside
|
|
@LordKay-sudo - there are conflicts on the branch. Also, could you please mark it as "resolved" upon resolution? Thank you |
MCP clients often pad copied ids; lookup should match the stored id. Signed-off-by: LordKay-sudo <lkandiro@gmail.com>
afterName is a string so production stays free of a storage compile dep. The combined runner lists MCP first so a broken name cannot hide behind declaration order. Signed-off-by: LordKay-sudo <lkandiro@gmail.com>
dice_get advertises an id "returned by recall, list, or store", but recall rendered through MemoryRetriever, which never emits one, so the documented recall-then-get flow could not actually be performed. Split ranking from rendering in MemoryRetriever and let the MCP surface format hits with the same helper dice_list uses; all four tools now share one output shape. limit was only coerced upward. An MCP caller is external and its limit is whatever the client model wrote, so clamp it to 100, mirroring the MAX_TOP_K RetrievalRouter already applies before it does any work. default-limit is validated against the same ceiling so a larger value fails startup instead of being silently truncated on every call. Signed-off-by: LordKay-sudo <lkandiro@gmail.com>
A live store or driver error used to reach the MCP client as exception text. Guard the four tools the way DiscoveryController sanitizes its 500s: caller validation still throws IllegalArgumentException, everything else becomes a cause-free generic failure so Cypher and bolt hosts cannot leak. get now answers a missing id and a foreign-context id the same way MemoryController collapses both into a 404, so the tool cannot confirm that an id it does not own exists. A query miss also restores the in-scope count and retry nudge that MemoryRetriever used to provide, without putting a raw context id into a slot meant for a human-readable topic. Signed-off-by: LordKay-sudo <lkandiro@gmail.com>
MCP clients can name any tenant; the per-call check only stops one call from crossing contexts. Authorization is the host MCP server's job. Also note that dice_store writes a fact with no mentions or provenance, so it is retrievable by vector and keyword only. Signed-off-by: LordKay-sudo <lkandiro@gmail.com>
Three modules pinned 5.4.0 locally. One property, one dependencyManagement entry. Signed-off-by: LordKay-sudo <lkandiro@gmail.com>
Optional tool fields were required in the published schema, docs used snake_case names the binder does not accept, and a store IllegalArgumentException could still reach the client. Mark query, limit, and confidence optional, document contextId/propositionId, sanitize every repository exception, and keep dice_store off unless writes-enabled is set. Get now shows status so a stale fact does not look active. Signed-off-by: LordKay-sudo <lkandiro@gmail.com>
533c5a8 to
f0a5169
Compare
|
@igordayen Rebased onto current main. Schema, writes, and get-detail threads are marked resolved. Left James's host context-policy thread open. |
igordayen
left a comment
There was a problem hiding this comment.
@LordKay-sudo - looks good; thank you for contributing!
|
@igordayen Thanks. |
Summary
DiceMcpToolswith simplifiedcontext_id-scoped parameters for external MCP clients (Claude Desktop, Cursor, etc.).dice-mcp-autoconfigureto export those tools via embabel-agentMcpToolExportwhenembabel.dice.mcp.enabled=true.dice_recall,dice_list,dice_store,dice_get.getrefuses an id that belongs to another context.Fixes #5
Motivation
Issue #5 asks to expose DICE tools, perhaps with simplified parameters. External MCP clients are stateless, so every tool takes an explicit
context_id. In-processMemory/DiscoveryToolsstill bake context in at construction; REST still takes it from the URL. MCP cannot do either, so isolation is a per-call check plusPropositionQuery.forContextId.Scope
This only exports
DiceMcpTools.DiscoveryToolsandGraphQueryToolsstay on the in-processasTools()path. Extraction stays onPropositionPipeline.What changed
com.embabel.dice.mcpDiceMcpTools,DiceMcpSupportdice-mcp-autoconfigurediceMcpToolExportbeanTest plan
DiceMcpToolsTest: store/get, recall, list filters, context isolation, constructor guardsDiceMcpAutoConfigurationTest: opt-in, missing store, property bind, custom beans, ConditionalOnClassmvn verify(all modules, zero skipped tests)