feat(mcp-tools): DecideskToolProvider — first per-app IMcpToolProvider#178
Merged
Conversation
First per-app implementation of OCA\OpenRegister\Mcp\IMcpToolProvider
(interface defined in openregister PR #1466). Exposes 5 governance
tools to the AI chat companion:
Reads:
- decidesk.listOpenActionItems — open action items, optionally
scoped to the current user (via OR ObjectService.findAll).
- decidesk.listRecentMeetings — last N meetings ordered by
createdAt desc, optional statusFilter.
- decidesk.getMeetingDetails — single meeting with agenda +
decisions + action items inlined as sub-sources.
Actions (lifecycle + mutation):
- decidesk.startMeeting — MeetingService.transition($uuid,
'open', $userId). Auth flowthrough enforced inside transition()
via ObjectService.saveObject (existing chair/admin guard).
- decidesk.addActionItem — TaskService.saveTask([...]) with
participant/admin auth check.
Architecture (per hydra ADR-034 + this change's design.md):
- Single DecideskToolProvider class at lib/Mcp/ delegates to existing
services via DI (MeetingService, TaskService, OR ObjectService).
- Tool ids namespaced as decidesk.{toolName}; OR's McpToolsService
rejects mismatches mechanically.
- Tool visibility: always expose; per-object auth enforced in
invokeTool() returning {isError: true, error: 'forbidden'} so the
LLM can explain.
- Every successful result includes a `sources` array (deep links)
capped at 20 for inline citation rendering in the widget.
- Six error codes: forbidden, not_found, invalid_state,
invalid_input, unknown_tool, internal_error.
Service container registration:
- Application.php registers alias
`OCA\OpenRegister\Mcp\IMcpToolProvider::decidesk` → the provider
class; OR's McpToolsService enumerates by alias prefix.
- No new composer dep — OR autoloads at runtime when installed,
same way decidesk's existing controllers already use
OCA\OpenRegister\Service\ObjectService.
Tests:
- 26 unit tests (tests/Unit/Mcp/DecideskToolProviderTest.php)
covering tool list shape, namespace enforcement, each tool happy
path, each tool's forbidden/not_found/invalid_state/invalid_input
paths, sources truncation at 20.
- Self-skipping integration tests (tests/Integration/Mcp/) for
end-to-end DI + service round-trips; require live Nextcloud +
OpenRegister.
- tests/Stubs/Mcp/IMcpToolProvider.php — interface stub loaded
by bootstrap-unit.php when the real interface isn't autoloadable
(i.e. CI without openregister installed).
- composer check:strict: ALL CHECKS PASSED (183 tests, 950
assertions, 37 skipped integration tests).
Static analysis suppressions:
- phpmd.baseline.xml: 12 entries for DecideskToolProvider complexity
(single-class design per D1; mitigation: extensive unit tests).
- phpstan.neon, psalm.xml: suppress `Undefined class` warnings for
the IMcpToolProvider interface (lives in openregister PR #1466,
not yet merged). Remove once openregister is bumped to a tag
shipping the interface.
Depends on openregister/ai-chat-companion-orchestrator (PR #1466).
Contributor
Quality Report — ConductionNL/decidesk @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 469/469 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ |
Coverage: 0% (0/115 statements)
Quality workflow — 2026-05-11 15:52 UTC
Download the full PDF report from the workflow artifacts.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First per-app implementation of
OCA\OpenRegister\Mcp\IMcpToolProvider(interface defined in openregister PR #1466, currently open). Exposes 5 governance tools to the AI chat companion that the widget surface (nc-vue) sends through OR's chat backend:Reads
decidesk.listOpenActionItems— open action items, optionally scoped to the current userdecidesk.listRecentMeetings— last N meetings, optional status filterdecidesk.getMeetingDetails— single meeting with agenda + decisions + action items inlinedActions (lifecycle + mutation)
decidesk.startMeeting— transitions a scheduled meeting to in-progress; chair/admin auth enforceddecidesk.addActionItem— creates an action item linked to a meeting; participant/admin authArchitecture
Single
DecideskToolProviderclass atlib/Mcp/DecideskToolProvider.phpdelegates to existing services (MeetingService,TaskService, ORObjectService) via DI. Tool IDs namespaceddecidesk.{toolName}; OR'sMcpToolsServiceenforces the namespace mechanically.Visibility model: every tool always exposed to the LLM; per-object auth check happens in
invokeTool()returning{isError: true, error: 'forbidden'}so the LLM can explain to the user. Six error codes total:forbidden,not_found,invalid_state,invalid_input,unknown_tool,internal_error.Source citations: every successful tool result includes a
sourcesarray of deep links (capped at 20). The widget'sCnAiMessageListrenders these as inline[view meeting]etc. links.Service container registration in
Application.php:No composer dep on openregister — OR autoloads at runtime via NC's app manager, same as the existing
OCA\OpenRegister\Service\ObjectServiceusage inMeetingController/MinutesController.Tests
tests/Unit/Mcp/DecideskToolProviderTest.phpcovering tool list shape, namespace enforcement, all 5 tools' happy paths + every error-code path + sources-array truncation.tests/Integration/Mcp/for live DI + service round-trips (require Nextcloud + OpenRegister installed).tests/Stubs/Mcp/IMcpToolProvider.phploaded bybootstrap-unit.phpwhen the real OR interface isn't on the autoloader.composer check:strict: ALL CHECKS PASSED (183 tests, 950 assertions, 37 skipped integration tests).Static analysis
phpmd.baseline.xml: 12 entries forDecideskToolProvidercomplexity (single-class design per design D1; mitigation: 26 unit tests).phpstan.neon,psalm.xml: suppressUndefined classforIMcpToolProvider(ships in openregister PR #1466, not merged yet). Remove once openregister is bumped to a tag that publishes the interface.Dependencies
Soft runtime dep on ConductionNL/openregister#1466. The provider is registered unconditionally; if OR isn't installed at runtime,
McpToolsServicesimply doesn't exist to enumerate it (the widget's health probe handles the no-op case).Spec
Full spec + design + tasks archived at
openspec/changes/archive/2026-05-11-decidesk-mcp-tools/and the capability spec promoted toopenspec/specs/mcp-tools/spec.md. 10 requirements (REQ-DMCP-001…010), 5 architectural decisions, 7 risks, 3 deferred questions resolved in-place during apply.Test plan
openspec validate decidesk-mcp-tools --strictpassescomposer check:strictALL CHECKS PASSEDdecidesk.*Pre-prod app per the standing memory rule (
pre-production apps: decidesk, mydash, ..., auto-merge OK), so this PR is admin-mergeable once you eyeball it.