Skip to content

feat(mcp-tools): ScholiqToolProvider — AI companion MCP tools - #39

Merged
rubenvdlinde merged 2 commits into
developmentfrom
feature/scholiq-mcp-tools
May 12, 2026
Merged

feat(mcp-tools): ScholiqToolProvider — AI companion MCP tools#39
rubenvdlinde merged 2 commits into
developmentfrom
feature/scholiq-mcp-tools

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Summary

First per-app implementation of OCA\OpenRegister\Mcp\IMcpToolProvider for Scholiq (LVS + LMS), per hydra ADR-034 + ADR-035. A minimal, privacy-conscious MVP skeleton — see the privacy posture below.

lib/Mcp/ScholiqToolProvider.php delegates to OpenRegister's ObjectService (the same pattern existing Scholiq controllers/handlers already use), injecting IUserSession + IGroupManager for the auth gate and LoggerInterface for error logging. Application.php registers the alias OCA\OpenRegister\Mcp\IMcpToolProvider::scholiqScholiqToolProvider so OR's McpToolsService discovers it.

Tools (2, read-only)

  • scholiq.listCourses — list Scholiq courses visible to you (course catalogue only — no enrolled-learner data). Optional limit (1–50, hard-capped at 20) and status filter (draft/published/archived).
  • scholiq.getCourseDetails — fetch one course by id/uuid/slug with its ordered module (Lesson) structure. Returns course metadata + module metadata only — never Enrolment, Attestation, Credential or learner objects.

Privacy posture (student data is privacy-sensitive)

Scholiq stores student records, so the MVP deliberately exposes only the two least privacy-sensitive tools — the course catalogue and a course's module structure. Neither tool returns any per-learner PII:

  • getCourseDetails returns the course + its modules; it never reads Enrolment/Attestation/Credential/learner objects. The unit test asserts no learner/enrolment/credential/attestation keys appear in the response.
  • Authorisation runs inside invokeTool(), after argument validation but before any data read. requireCourseReadAccess() rejects anonymous callers (no unconditional return true, no catch(\Throwable) swallowing the verdict); OpenRegister's RBAC layer inside ObjectService is the second, per-object gate (so _rbac/_multitenancy stay at their default true).
  • invokeTool() never throws — every failure path returns a structured error array (invalid_arguments / forbidden / not_found / internal_error / unknown_tool).

Student-data tools (learner progress, enrolments, attestations, credentials, compliance coverage — the rest of #36) are deferred to a follow-up that wires proper per-student authorisation: the caller must be a teacher of that student's group, the student themself, or an admin.

Stub

Until openregister PR #1466 (ai-chat-companion-orchestrator) ships the real interface, Scholiq implements the stub at tests/Stubs/Mcp/IMcpToolProvider.php (loaded by tests/bootstrap.php and tests/bootstrap-unit.php when the interface isn't autoloadable). psalm.xml suppresses the Undefined-class warning; phpstan.neon scans tests/Stubs so the cross-app interface resolves (and broadens the existing OpenRegister ignore patterns, which also clears some pre-existing CredentialIssuanceHandler/XapiCompletionHandler noise).

Tests & quality

  • tests/Unit/Mcp/ScholiqToolProviderTest.php — 8 tests / 58 assertions: getAppId() === 'scholiq', 2 descriptors with scholiq. ids + non-empty descriptions + valid inputSchema, invokeTool('scholiq.bogus', []) returns an error array (no throw), argument validation, the anonymous-caller forbidden path, and a happy-path getCourseDetails asserting no learner-PII leaks. All 8 pass in the Nextcloud container.
  • composer phpcs — clean. composer phpmd — clean for the new file. composer phpstan — no new errors (6 remaining are pre-existing in AuditPackExportController/HealthController). composer check — ALL CHECKS PASSED.
  • npm run build — succeeds (size warnings only).

Done vs deferred

Done: the 2 read-only course tools, DI registration, stub + bootstrap wiring, unit tests, static-analysis suppressions, @conduction/nextcloud-vue bump to ^1.0.0-beta.30 (scholiq already mounts CnAppRoot, which surfaces the AI chat companion from the manifest's openregister dependency — no extra scaffolding needed).

Deferred:

  • The remaining tools from AI companion: implement the MCP tool MVP for scholiq #36 — learner progress, enrolment, attestation, credential, compliance-coverage tools — pending proper per-student authorisation (follow-up PR).
  • No new widget scaffolding: scholiq already uses CnAppRoot, so the companion mount comes "for free" via the dep bump + manifest.

Refs #36 · ADR-034 · ADR-035

MCP coverage

Adds tool: scholiq.listCourses
Adds tool: scholiq.getCourseDetails

…eleton)

First per-app implementation of OCA\OpenRegister\Mcp\IMcpToolProvider for
Scholiq (LVS + LMS). Scholiq handles student data, which is privacy
sensitive — so the MVP deliberately ships ONLY the two least sensitive,
read-only tools (the course catalogue and a course's module structure):

- scholiq.listCourses     — list visible courses (catalogue only, no
  learner data); optional limit (1-50, capped at 20) + status filter.
- scholiq.getCourseDetails — one course by id/uuid/slug with its ordered
  module (Lesson) structure; course + module metadata only — never
  Enrolment, Attestation, Credential or learner objects.

Tools that touch learner records are deferred to a follow-up that wires
proper per-student authorisation (a teacher of that learner's group, the
learner themself, or an admin).

Architecture (hydra ADR-034 + ADR-035):
- lib/Mcp/ScholiqToolProvider.php delegates to OR's ObjectService (same
  pattern existing scholiq controllers/handlers already use); injects
  IUserSession + IGroupManager for the auth gate, LoggerInterface for
  error logging.
- Tool ids namespaced scholiq.{tool}; getTools() always returns the full
  catalogue; per-request authorisation runs in invokeTool() AFTER argument
  validation but BEFORE business logic. requireCourseReadAccess() rejects
  anonymous callers; OpenRegister RBAC inside ObjectService is the second,
  per-object gate (so _rbac/_multitenancy stay at their default true).
- invokeTool() never throws — all failure paths return a structured error
  array (invalid_arguments / forbidden / not_found / internal_error /
  unknown_tool).
- Application.php registers the alias
  'OCA\OpenRegister\Mcp\IMcpToolProvider::scholiq' → ScholiqToolProvider.

Until openregister PR #1466 (ai-chat-companion-orchestrator) ships the
real interface, Scholiq implements the stub at
tests/Stubs/Mcp/IMcpToolProvider.php (loaded by tests/bootstrap.php and
tests/bootstrap-unit.php when the interface isn't autoloadable).

Tests: tests/Unit/Mcp/ScholiqToolProviderTest.php — 8 tests / 58
assertions: getAppId, catalogue shape, unknown-tool error envelope (no
throw), argument validation, the anonymous-caller forbidden path, and a
happy-path getCourseDetails that asserts no learner-PII keys leak.

Static analysis: psalm.xml suppresses Undefined-class for the
IMcpToolProvider interface; phpstan.neon scans tests/Stubs so the
cross-app interface resolves, plus broadens the existing OpenRegister
ignore patterns ('unknown interface', 'has invalid type') — which also
clears some pre-existing CredentialIssuanceHandler/XapiCompletionHandler
noise.

Widget mount: bumps @conduction/nextcloud-vue to ^1.0.0-beta.30 (scholiq
already mounts CnAppRoot, which surfaces the AI chat companion from the
manifest's openregister dependency — no extra scaffolding needed).

Refs #36
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/scholiq @ 92a876c

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm ✅ 428/428
PHPUnit
Newman
Playwright ⏭️

Quality workflow — 2026-05-12 05:13 UTC

Download the full PDF report from the workflow artifacts.

…q-mcp-tools

# Conflicts:
#	lib/AppInfo/Application.php
#	package-lock.json
#	package.json
@rubenvdlinde
rubenvdlinde merged commit de134bb into development May 12, 2026
30 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/scholiq @ aa0c6f0

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm ✅ 429/429
PHPUnit
Newman
Playwright ⏭️

Quality workflow — 2026-05-12 12:39 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