Skip to content

Commit de134bb

Browse files
authored
feat(mcp-tools): ScholiqToolProvider — AI companion MCP tools (MVP skeleton) (#39)
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
1 parent f4583b4 commit de134bb

8 files changed

Lines changed: 1056 additions & 0 deletions

File tree

lib/AppInfo/Application.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use OCA\Scholiq\Listener\CredentialIssuanceHandler;
3030
use OCA\Scholiq\Listener\DataExchangeRunHandler;
3131
use OCA\Scholiq\Listener\DeepLinkRegistrationListener;
32+
use OCA\Scholiq\Mcp\ScholiqToolProvider;
3233
use OCA\Scholiq\Listener\GradeRollupHandler;
3334
use OCA\Scholiq\Listener\LearningPlanEvaluationHandler;
3435
use OCA\OpenRegister\Event\DeepLinkRegistrationEvent;
@@ -104,6 +105,16 @@ public function register(IRegistrationContext $context): void
104105
listener: CredentialIssuanceHandler::class
105106
);
106107

108+
// Register ScholiqToolProvider as the MCP tool provider for the AI Chat Companion.
109+
// The alias key 'OCA\OpenRegister\Mcp\IMcpToolProvider::scholiq' is the format
110+
// that OR's McpToolsService enumerates to discover per-app providers.
111+
// The interface ships in openregister PR #1466 (ai-chat-companion-orchestrator);
112+
// until merged, Scholiq implements the stub at tests/Stubs/Mcp/IMcpToolProvider.php.
113+
$context->registerServiceAlias(
114+
'OCA\\OpenRegister\\Mcp\\IMcpToolProvider::scholiq',
115+
ScholiqToolProvider::class
116+
);
117+
107118
// ADR-031 legitimate exception: GradeEntry.published → FinalGrade recompute bridge,
108119
// and AssessmentResult.graded → concept GradeEntry creation bridge.
109120
// Listens for OR's ObjectTransitionedEvent; the GradeRollupHandler filters to the

0 commit comments

Comments
 (0)