Skip to content

feat(mcp-tools): DecideskToolProvider — first per-app IMcpToolProvider#178

Merged
rubenvdlinde merged 1 commit into
developmentfrom
feature/decidesk-mcp-tools
May 11, 2026
Merged

feat(mcp-tools): DecideskToolProvider — first per-app IMcpToolProvider#178
rubenvdlinde merged 1 commit into
developmentfrom
feature/decidesk-mcp-tools

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

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 user
  • decidesk.listRecentMeetings — last N meetings, optional status filter
  • decidesk.getMeetingDetails — single meeting with agenda + decisions + action items inlined

Actions (lifecycle + mutation)

  • decidesk.startMeeting — transitions a scheduled meeting to in-progress; chair/admin auth enforced
  • decidesk.addActionItem — creates an action item linked to a meeting; participant/admin auth

Architecture

Single DecideskToolProvider class at lib/Mcp/DecideskToolProvider.php delegates to existing services (MeetingService, TaskService, OR ObjectService) via DI. Tool IDs namespaced decidesk.{toolName}; OR's McpToolsService enforces 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 sources array of deep links (capped at 20). The widget's CnAiMessageList renders these as inline [view meeting] etc. links.

Service container registration in Application.php:

$context->registerServiceAlias(
    'OCA\\OpenRegister\\Mcp\\IMcpToolProvider::decidesk',
    DecideskToolProvider::class,
);

No composer dep on openregister — OR autoloads at runtime via NC's app manager, same as the existing OCA\OpenRegister\Service\ObjectService usage in MeetingController / MinutesController.

Tests

  • 26 unit tests in tests/Unit/Mcp/DecideskToolProviderTest.php covering tool list shape, namespace enforcement, all 5 tools' happy paths + every error-code path + sources-array truncation.
  • Self-skipping integration tests in tests/Integration/Mcp/ for live DI + service round-trips (require Nextcloud + OpenRegister installed).
  • Interface stub in tests/Stubs/Mcp/IMcpToolProvider.php loaded by bootstrap-unit.php when 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 for DecideskToolProvider complexity (single-class design per design D1; mitigation: 26 unit tests).
  • phpstan.neon, psalm.xml: suppress Undefined class for IMcpToolProvider (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, McpToolsService simply 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 to openspec/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 --strict passes
  • composer check:strict ALL CHECKS PASSED
  • All 5 tools have happy-path + every-error-code unit tests
  • Tool ids namespaced decidesk.*
  • Live end-to-end smoke test against an LLM via the AI chat widget (blocked on openregister#1466 merging + Ollama health)

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.

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).
@rubenvdlinde rubenvdlinde merged commit b64544f into development May 11, 2026
14 checks passed
@rubenvdlinde rubenvdlinde deleted the feature/decidesk-mcp-tools branch May 11, 2026 15:49
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/decidesk @ 178050e

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant