@massteck/aloc-mcp-server 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -11
- package/dist/client.d.ts +94 -0
- package/dist/client.js +121 -2
- package/dist/index.js +136 -0
- package/package.json +1 -1
- package/src/client.ts +132 -2
- package/src/index.ts +146 -0
package/README.md
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
# ALOC Model Context Protocol (MCP) Server
|
|
2
2
|
|
|
3
|
-
Official Model Context Protocol (MCP) server for **ALOC** — providing African educational examination content, curriculum structures,
|
|
3
|
+
Official Model Context Protocol (MCP) server for **ALOC** (`@massteck/aloc-mcp-server`) — providing African educational examination content, curriculum structures, AI-enriched explanations, and autonomous discovery tools to AI agents and LLM clients (Claude Desktop, Cursor, LangChain, AI Tutors).
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Features & Tools
|
|
8
8
|
|
|
9
|
-
| Tool |
|
|
9
|
+
| Tool | Category | Credits | Description |
|
|
10
10
|
| :--- | :---: | :---: | :--- |
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
11
|
+
| `aloc_get_platform_overview` | Discovery | **0 credits** | Discover supported African exam bodies (WAEC, JAMB, NECO), subjects, year coverage, and API layer rules. |
|
|
12
|
+
| `aloc_get_sitemap` | Discovery | **0 credits** | Direct URLs to developer portal, documentation, and error guides. |
|
|
13
|
+
| `aloc_get_subject_years` | Discovery | **0 credits** | Available examination years for a specific subject & body. |
|
|
14
|
+
| `aloc_check_credit_balance` | Account | **0 credits** | Check remaining API credit balance & authentication status. |
|
|
15
|
+
| `aloc_search_questions` | Content | **1–3 credits** | Search curriculum-aligned examination questions with options, topics, and difficulty. |
|
|
16
|
+
| `aloc_get_question_explanation` | Learning | **10 credits** | Retrieve step-by-step solutions, misconceptions, and remedial hints. |
|
|
17
|
+
| `aloc_get_curriculum_taxonomy` | Curriculum | **Free/3 credits** | Explore the topic taxonomy and syllabus hierarchy. |
|
|
18
|
+
| `aloc_find_similar_questions` | Intelligence | **10 credits** | Find vector-similar exam questions. |
|
|
19
|
+
| `aloc_report_question_issue` | Quality | **0 credits** | Report a typo or disputed answer to ALOC moderation. |
|
|
15
20
|
|
|
16
21
|
---
|
|
17
22
|
|
|
@@ -42,11 +47,24 @@ Add the following to your `claude_desktop_config.json`:
|
|
|
42
47
|
}
|
|
43
48
|
```
|
|
44
49
|
|
|
45
|
-
### 3. Configuration (Cursor
|
|
50
|
+
### 3. Configuration (Cursor IDE)
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
Add the following to your `.cursor/mcp.json`:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"aloc": {
|
|
58
|
+
"command": "npx",
|
|
59
|
+
"args": ["-y", "@massteck/aloc-mcp-server"],
|
|
60
|
+
"env": {
|
|
61
|
+
"ALOC_API_KEY": "your_aloc_api_key_here",
|
|
62
|
+
"ALOC_AGENT_FRAMEWORK": "cursor"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
50
68
|
|
|
51
69
|
---
|
|
52
70
|
|
|
@@ -56,4 +74,14 @@ Every MCP tool call:
|
|
|
56
74
|
1. Passes an authenticated API key via `X-API-Key`.
|
|
57
75
|
2. Sends client telemetry headers (`X-Client-Type: mcp`, `X-Is-Agent: true`).
|
|
58
76
|
3. Deducts metered credits matching the ALOC pricing tiers.
|
|
59
|
-
4. Enforces request limits (maximum 20 items per search) to prevent bulk
|
|
77
|
+
4. Enforces request limits (maximum 20 items per search) to prevent bulk harvesting.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Documentation & Support
|
|
82
|
+
|
|
83
|
+
For comprehensive integration guides, interactive API playground, and developer credentials, visit the official ALOC Developer Portal:
|
|
84
|
+
|
|
85
|
+
* 📖 **Documentation**: [https://aloc.com.ng/docs](https://aloc.com.ng/docs)
|
|
86
|
+
* 🎮 **API Playground**: [https://aloc.com.ng/playground](https://aloc.com.ng/playground)
|
|
87
|
+
* 📧 **Support**: [aloc.mass@gmail.com](mailto:aloc.mass@gmail.com)
|
package/dist/client.d.ts
CHANGED
|
@@ -6,7 +6,72 @@ export interface AlocClientConfig {
|
|
|
6
6
|
export declare class AlocClient {
|
|
7
7
|
private http;
|
|
8
8
|
private apiKey;
|
|
9
|
+
private baseUrl;
|
|
9
10
|
constructor(config?: AlocClientConfig);
|
|
11
|
+
/**
|
|
12
|
+
* Return high-level self-describing overview of the ALOC platform and coverage.
|
|
13
|
+
*/
|
|
14
|
+
getPlatformOverview(): {
|
|
15
|
+
platform: string;
|
|
16
|
+
description: string;
|
|
17
|
+
supportedExaminationBodies: {
|
|
18
|
+
code: string;
|
|
19
|
+
name: string;
|
|
20
|
+
}[];
|
|
21
|
+
yearRange: string;
|
|
22
|
+
supportedSubjects: string[];
|
|
23
|
+
apiLayers: {
|
|
24
|
+
L1: {
|
|
25
|
+
name: string;
|
|
26
|
+
cost: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
L2: {
|
|
30
|
+
name: string;
|
|
31
|
+
cost: string;
|
|
32
|
+
description: string;
|
|
33
|
+
};
|
|
34
|
+
L3: {
|
|
35
|
+
name: string;
|
|
36
|
+
cost: string;
|
|
37
|
+
description: string;
|
|
38
|
+
};
|
|
39
|
+
L4: {
|
|
40
|
+
name: string;
|
|
41
|
+
cost: string;
|
|
42
|
+
description: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
guidelines: {
|
|
46
|
+
pagination: string;
|
|
47
|
+
filtering: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Return sitemap links for developer documentation and portal.
|
|
52
|
+
*/
|
|
53
|
+
getSitemap(): {
|
|
54
|
+
portal: {
|
|
55
|
+
home: string;
|
|
56
|
+
dashboard: string;
|
|
57
|
+
apiKeys: string;
|
|
58
|
+
usage: string;
|
|
59
|
+
billing: string;
|
|
60
|
+
};
|
|
61
|
+
documentation: {
|
|
62
|
+
introduction: string;
|
|
63
|
+
authentication: string;
|
|
64
|
+
questions: string;
|
|
65
|
+
metadata: string;
|
|
66
|
+
explanations: string;
|
|
67
|
+
mcp: string;
|
|
68
|
+
errors: string;
|
|
69
|
+
openapiSpec: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Search examination questions with filters.
|
|
74
|
+
*/
|
|
10
75
|
getQuestions(params: {
|
|
11
76
|
subject: string;
|
|
12
77
|
examType?: string;
|
|
@@ -15,11 +80,40 @@ export declare class AlocClient {
|
|
|
15
80
|
difficulty?: string;
|
|
16
81
|
limit?: number;
|
|
17
82
|
}): Promise<any>;
|
|
83
|
+
/**
|
|
84
|
+
* Retrieve step-by-step AI explanation.
|
|
85
|
+
*/
|
|
18
86
|
getQuestionExplanation(questionId: string, depth?: string): Promise<any>;
|
|
87
|
+
/**
|
|
88
|
+
* Explore curriculum topics and taxonomy hierarchy.
|
|
89
|
+
*/
|
|
19
90
|
getCurriculumTaxonomy(params?: {
|
|
20
91
|
subject?: string;
|
|
21
92
|
examType?: string;
|
|
22
93
|
}): Promise<any>;
|
|
94
|
+
/**
|
|
95
|
+
* Find vector-similar exam questions.
|
|
96
|
+
*/
|
|
23
97
|
getSimilarQuestions(questionId: string, limit?: number): Promise<any>;
|
|
98
|
+
/**
|
|
99
|
+
* List available subjects on ALOC.
|
|
100
|
+
*/
|
|
24
101
|
getSubjects(): Promise<any>;
|
|
102
|
+
/**
|
|
103
|
+
* List examination years available for a specific subject.
|
|
104
|
+
*/
|
|
105
|
+
getSubjectYears(subject: string, examType?: string): Promise<any>;
|
|
106
|
+
/**
|
|
107
|
+
* Check credit balance and subscription status.
|
|
108
|
+
*/
|
|
109
|
+
checkCreditBalance(): Promise<{
|
|
110
|
+
creditsRemaining: string | number;
|
|
111
|
+
creditsUsedLastRequest: number;
|
|
112
|
+
authenticated: boolean;
|
|
113
|
+
apiKeyPrefix: string;
|
|
114
|
+
}>;
|
|
115
|
+
/**
|
|
116
|
+
* Report an error, typo, or disputed answer on a question.
|
|
117
|
+
*/
|
|
118
|
+
reportQuestionIssue(questionId: string, type: "question" | "option_a" | "option_b" | "option_c" | "option_d" | "correct_answer" | "explanation" | "other", message: string, reporterName?: string): Promise<any>;
|
|
25
119
|
}
|
package/dist/client.js
CHANGED
|
@@ -8,14 +8,15 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
8
8
|
class AlocClient {
|
|
9
9
|
http;
|
|
10
10
|
apiKey;
|
|
11
|
+
baseUrl;
|
|
11
12
|
constructor(config = {}) {
|
|
12
13
|
this.apiKey = config.apiKey || process.env.ALOC_API_KEY || "";
|
|
13
|
-
|
|
14
|
+
this.baseUrl = config.baseUrl || process.env.ALOC_BASE_URL || "https://dev.aloc.com.ng/api/v1";
|
|
14
15
|
if (!this.apiKey) {
|
|
15
16
|
console.error("[WARN] ALOC_API_KEY is not set. Authenticated MCP requests will fail until an API key is provided.");
|
|
16
17
|
}
|
|
17
18
|
this.http = axios_1.default.create({
|
|
18
|
-
baseURL: baseUrl,
|
|
19
|
+
baseURL: this.baseUrl,
|
|
19
20
|
headers: {
|
|
20
21
|
"Content-Type": "application/json",
|
|
21
22
|
Accept: "application/json",
|
|
@@ -28,29 +29,147 @@ class AlocClient {
|
|
|
28
29
|
timeout: 15000,
|
|
29
30
|
});
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Return high-level self-describing overview of the ALOC platform and coverage.
|
|
34
|
+
*/
|
|
35
|
+
getPlatformOverview() {
|
|
36
|
+
return {
|
|
37
|
+
platform: "ALOC Educational Assessment Infrastructure",
|
|
38
|
+
description: "Assessment content infrastructure platform for African EdTech. Provides structured, enriched, curriculum-aligned examination content and deep metadata through versioned APIs.",
|
|
39
|
+
supportedExaminationBodies: [
|
|
40
|
+
{ code: "waec", name: "West African Examinations Council (Senior Secondary)" },
|
|
41
|
+
{ code: "jamb", name: "Joint Admissions and Matriculation Board (UTME)" },
|
|
42
|
+
{ code: "neco", name: "National Examinations Council" },
|
|
43
|
+
{ code: "post-utme", name: "Institution-specific Post-UTME" },
|
|
44
|
+
],
|
|
45
|
+
yearRange: "2010 – 2024",
|
|
46
|
+
supportedSubjects: [
|
|
47
|
+
"mathematics",
|
|
48
|
+
"english",
|
|
49
|
+
"biology",
|
|
50
|
+
"physics",
|
|
51
|
+
"chemistry",
|
|
52
|
+
"economics",
|
|
53
|
+
"commerce",
|
|
54
|
+
"accounting",
|
|
55
|
+
"government",
|
|
56
|
+
"literature",
|
|
57
|
+
"crk",
|
|
58
|
+
"irk",
|
|
59
|
+
"civic-education",
|
|
60
|
+
"geography",
|
|
61
|
+
"agricultural-science",
|
|
62
|
+
"further-mathematics",
|
|
63
|
+
"history",
|
|
64
|
+
"insurance",
|
|
65
|
+
],
|
|
66
|
+
apiLayers: {
|
|
67
|
+
L1: { name: "Raw Questions", cost: "1 credit", description: "Direct question fetch and session retrieval" },
|
|
68
|
+
L2: { name: "Metadata & Taxonomy", cost: "3 credits", description: "Topic taxonomy, difficulty score, and curriculum mapping" },
|
|
69
|
+
L3: { name: "Explanations", cost: "10 credits", description: "Step-by-step solutions, misconceptions, and remedial hints" },
|
|
70
|
+
L4: { name: "Intelligence", cost: "10 credits", description: "Semantic vector similarity, question generation, and trend analytics" },
|
|
71
|
+
},
|
|
72
|
+
guidelines: {
|
|
73
|
+
pagination: "Cursor-based pagination with max 20 items per request (50 items max via direct REST)",
|
|
74
|
+
filtering: "At least one filter (subject, examType, or year) is required",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Return sitemap links for developer documentation and portal.
|
|
80
|
+
*/
|
|
81
|
+
getSitemap() {
|
|
82
|
+
return {
|
|
83
|
+
portal: {
|
|
84
|
+
home: "https://aloc.com.ng",
|
|
85
|
+
dashboard: "https://aloc.com.ng/dashboard",
|
|
86
|
+
apiKeys: "https://aloc.com.ng/dashboard/api-keys",
|
|
87
|
+
usage: "https://aloc.com.ng/dashboard/usage",
|
|
88
|
+
billing: "https://aloc.com.ng/dashboard/billing",
|
|
89
|
+
},
|
|
90
|
+
documentation: {
|
|
91
|
+
introduction: "https://aloc.com.ng/docs/getting-started",
|
|
92
|
+
authentication: "https://aloc.com.ng/docs/authentication",
|
|
93
|
+
questions: "https://aloc.com.ng/docs/questions",
|
|
94
|
+
metadata: "https://aloc.com.ng/docs/metadata",
|
|
95
|
+
explanations: "https://aloc.com.ng/docs/explanations",
|
|
96
|
+
mcp: "https://aloc.com.ng/docs/mcp",
|
|
97
|
+
errors: "https://aloc.com.ng/docs/errors",
|
|
98
|
+
openapiSpec: `${this.baseUrl}/docs`,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Search examination questions with filters.
|
|
104
|
+
*/
|
|
31
105
|
async getQuestions(params) {
|
|
32
106
|
const response = await this.http.get("/questions", { params });
|
|
33
107
|
return response.data;
|
|
34
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Retrieve step-by-step AI explanation.
|
|
111
|
+
*/
|
|
35
112
|
async getQuestionExplanation(questionId, depth) {
|
|
36
113
|
const response = await this.http.post(`/questions/${questionId}/explain`, {
|
|
37
114
|
depth: depth || "step_by_step",
|
|
38
115
|
});
|
|
39
116
|
return response.data;
|
|
40
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Explore curriculum topics and taxonomy hierarchy.
|
|
120
|
+
*/
|
|
41
121
|
async getCurriculumTaxonomy(params) {
|
|
42
122
|
const response = await this.http.get("/curriculum", { params });
|
|
43
123
|
return response.data;
|
|
44
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Find vector-similar exam questions.
|
|
127
|
+
*/
|
|
45
128
|
async getSimilarQuestions(questionId, limit) {
|
|
46
129
|
const response = await this.http.get(`/questions/${questionId}/similar`, {
|
|
47
130
|
params: { limit: limit || 3 },
|
|
48
131
|
});
|
|
49
132
|
return response.data;
|
|
50
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* List available subjects on ALOC.
|
|
136
|
+
*/
|
|
51
137
|
async getSubjects() {
|
|
52
138
|
const response = await this.http.get("/subjects");
|
|
53
139
|
return response.data;
|
|
54
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* List examination years available for a specific subject.
|
|
143
|
+
*/
|
|
144
|
+
async getSubjectYears(subject, examType) {
|
|
145
|
+
const response = await this.http.get(`/subjects/${encodeURIComponent(subject)}/years`, {
|
|
146
|
+
params: examType ? { examType } : undefined,
|
|
147
|
+
});
|
|
148
|
+
return response.data;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Check credit balance and subscription status.
|
|
152
|
+
*/
|
|
153
|
+
async checkCreditBalance() {
|
|
154
|
+
const response = await this.http.get("/subjects");
|
|
155
|
+
const headers = response.headers;
|
|
156
|
+
return {
|
|
157
|
+
creditsRemaining: headers["x-credits-remaining"] ? parseInt(headers["x-credits-remaining"], 10) : "active",
|
|
158
|
+
creditsUsedLastRequest: headers["x-credits-used"] ? parseInt(headers["x-credits-used"], 10) : 0,
|
|
159
|
+
authenticated: true,
|
|
160
|
+
apiKeyPrefix: this.apiKey.substring(0, 10) + "••••••••",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Report an error, typo, or disputed answer on a question.
|
|
165
|
+
*/
|
|
166
|
+
async reportQuestionIssue(questionId, type, message, reporterName) {
|
|
167
|
+
const response = await this.http.post(`/questions/${questionId}/report`, {
|
|
168
|
+
type,
|
|
169
|
+
message,
|
|
170
|
+
reporterName: reporterName || "ALOC AI Agent",
|
|
171
|
+
});
|
|
172
|
+
return response.data;
|
|
173
|
+
}
|
|
55
174
|
}
|
|
56
175
|
exports.AlocClient = AlocClient;
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,49 @@ const server = new index_js_1.Server({
|
|
|
18
18
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
19
19
|
return {
|
|
20
20
|
tools: [
|
|
21
|
+
{
|
|
22
|
+
name: "aloc_get_platform_overview",
|
|
23
|
+
description: "Discover ALOC assessment infrastructure overview, supported African exam bodies (WAEC, JAMB, NECO), subject list, year coverage (2010–2024), and API layers. Free (0 credits).",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "aloc_get_sitemap",
|
|
31
|
+
description: "Get complete navigation sitemap and direct links to ALOC developer portal, documentation, and error code guides. Free (0 credits).",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "aloc_get_subject_years",
|
|
39
|
+
description: "List all available examination years for a specific subject and exam body. Free (0 credits).",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
subject: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Subject name (e.g. 'mathematics', 'physics', 'english')",
|
|
46
|
+
},
|
|
47
|
+
examType: {
|
|
48
|
+
type: "string",
|
|
49
|
+
enum: ["waec", "jamb", "neco", "post-utme"],
|
|
50
|
+
description: "Optional examination body filter",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
required: ["subject"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "aloc_check_credit_balance",
|
|
58
|
+
description: "Check the developer's remaining ALOC API credit balance and authentication status. Free (0 credits).",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
21
64
|
{
|
|
22
65
|
name: "aloc_search_questions",
|
|
23
66
|
description: "Search and retrieve curriculum-aligned examination questions (WAEC, JAMB, NECO) with options, metadata, and difficulty levels. Consumes 1-3 ALOC credits.",
|
|
@@ -112,6 +155,42 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
112
155
|
required: ["questionId"],
|
|
113
156
|
},
|
|
114
157
|
},
|
|
158
|
+
{
|
|
159
|
+
name: "aloc_report_question_issue",
|
|
160
|
+
description: "Report a typo, incorrect answer key, or ambiguity on a specific question to ALOC quality moderation. Free (0 credits).",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
questionId: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "UUID of the question with an issue",
|
|
167
|
+
},
|
|
168
|
+
type: {
|
|
169
|
+
type: "string",
|
|
170
|
+
enum: [
|
|
171
|
+
"question",
|
|
172
|
+
"option_a",
|
|
173
|
+
"option_b",
|
|
174
|
+
"option_c",
|
|
175
|
+
"option_d",
|
|
176
|
+
"correct_answer",
|
|
177
|
+
"explanation",
|
|
178
|
+
"other",
|
|
179
|
+
],
|
|
180
|
+
description: "Category of issue identified",
|
|
181
|
+
},
|
|
182
|
+
message: {
|
|
183
|
+
type: "string",
|
|
184
|
+
description: "Detailed description of the issue or mathematical discrepancy",
|
|
185
|
+
},
|
|
186
|
+
reporterName: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "Optional name of reporting agent or developer",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
required: ["questionId", "type", "message"],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
115
194
|
],
|
|
116
195
|
};
|
|
117
196
|
});
|
|
@@ -120,6 +199,51 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
120
199
|
const { name, arguments: args } = request.params;
|
|
121
200
|
try {
|
|
122
201
|
switch (name) {
|
|
202
|
+
case "aloc_get_platform_overview": {
|
|
203
|
+
const result = client.getPlatformOverview();
|
|
204
|
+
return {
|
|
205
|
+
content: [
|
|
206
|
+
{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: JSON.stringify(result, null, 2),
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
case "aloc_get_sitemap": {
|
|
214
|
+
const result = client.getSitemap();
|
|
215
|
+
return {
|
|
216
|
+
content: [
|
|
217
|
+
{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: JSON.stringify(result, null, 2),
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
case "aloc_get_subject_years": {
|
|
225
|
+
const { subject, examType } = (args || {});
|
|
226
|
+
const result = await client.getSubjectYears(subject, examType);
|
|
227
|
+
return {
|
|
228
|
+
content: [
|
|
229
|
+
{
|
|
230
|
+
type: "text",
|
|
231
|
+
text: JSON.stringify(result, null, 2),
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
case "aloc_check_credit_balance": {
|
|
237
|
+
const result = await client.checkCreditBalance();
|
|
238
|
+
return {
|
|
239
|
+
content: [
|
|
240
|
+
{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: JSON.stringify(result, null, 2),
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
123
247
|
case "aloc_search_questions": {
|
|
124
248
|
const { subject, examType, year, topic, difficulty, limit } = (args || {});
|
|
125
249
|
const result = await client.getQuestions({
|
|
@@ -175,6 +299,18 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
175
299
|
],
|
|
176
300
|
};
|
|
177
301
|
}
|
|
302
|
+
case "aloc_report_question_issue": {
|
|
303
|
+
const { questionId, type, message, reporterName } = (args || {});
|
|
304
|
+
const result = await client.reportQuestionIssue(questionId, type, message, reporterName);
|
|
305
|
+
return {
|
|
306
|
+
content: [
|
|
307
|
+
{
|
|
308
|
+
type: "text",
|
|
309
|
+
text: JSON.stringify(result, null, 2),
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
178
314
|
default:
|
|
179
315
|
throw new Error(`Unknown ALOC tool: ${name}`);
|
|
180
316
|
}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -9,10 +9,11 @@ export interface AlocClientConfig {
|
|
|
9
9
|
export class AlocClient {
|
|
10
10
|
private http: AxiosInstance;
|
|
11
11
|
private apiKey: string;
|
|
12
|
+
private baseUrl: string;
|
|
12
13
|
|
|
13
14
|
constructor(config: AlocClientConfig = {}) {
|
|
14
15
|
this.apiKey = config.apiKey || process.env.ALOC_API_KEY || "";
|
|
15
|
-
|
|
16
|
+
this.baseUrl = config.baseUrl || process.env.ALOC_BASE_URL || "https://dev.aloc.com.ng/api/v1";
|
|
16
17
|
|
|
17
18
|
if (!this.apiKey) {
|
|
18
19
|
console.error(
|
|
@@ -21,7 +22,7 @@ export class AlocClient {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
this.http = axios.create({
|
|
24
|
-
baseURL: baseUrl,
|
|
25
|
+
baseURL: this.baseUrl,
|
|
25
26
|
headers: {
|
|
26
27
|
"Content-Type": "application/json",
|
|
27
28
|
Accept: "application/json",
|
|
@@ -35,6 +36,82 @@ export class AlocClient {
|
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Return high-level self-describing overview of the ALOC platform and coverage.
|
|
41
|
+
*/
|
|
42
|
+
public getPlatformOverview() {
|
|
43
|
+
return {
|
|
44
|
+
platform: "ALOC Educational Assessment Infrastructure",
|
|
45
|
+
description:
|
|
46
|
+
"Assessment content infrastructure platform for African EdTech. Provides structured, enriched, curriculum-aligned examination content and deep metadata through versioned APIs.",
|
|
47
|
+
supportedExaminationBodies: [
|
|
48
|
+
{ code: "waec", name: "West African Examinations Council (Senior Secondary)" },
|
|
49
|
+
{ code: "jamb", name: "Joint Admissions and Matriculation Board (UTME)" },
|
|
50
|
+
{ code: "neco", name: "National Examinations Council" },
|
|
51
|
+
{ code: "post-utme", name: "Institution-specific Post-UTME" },
|
|
52
|
+
],
|
|
53
|
+
yearRange: "2010 – 2024",
|
|
54
|
+
supportedSubjects: [
|
|
55
|
+
"mathematics",
|
|
56
|
+
"english",
|
|
57
|
+
"biology",
|
|
58
|
+
"physics",
|
|
59
|
+
"chemistry",
|
|
60
|
+
"economics",
|
|
61
|
+
"commerce",
|
|
62
|
+
"accounting",
|
|
63
|
+
"government",
|
|
64
|
+
"literature",
|
|
65
|
+
"crk",
|
|
66
|
+
"irk",
|
|
67
|
+
"civic-education",
|
|
68
|
+
"geography",
|
|
69
|
+
"agricultural-science",
|
|
70
|
+
"further-mathematics",
|
|
71
|
+
"history",
|
|
72
|
+
"insurance",
|
|
73
|
+
],
|
|
74
|
+
apiLayers: {
|
|
75
|
+
L1: { name: "Raw Questions", cost: "1 credit", description: "Direct question fetch and session retrieval" },
|
|
76
|
+
L2: { name: "Metadata & Taxonomy", cost: "3 credits", description: "Topic taxonomy, difficulty score, and curriculum mapping" },
|
|
77
|
+
L3: { name: "Explanations", cost: "10 credits", description: "Step-by-step solutions, misconceptions, and remedial hints" },
|
|
78
|
+
L4: { name: "Intelligence", cost: "10 credits", description: "Semantic vector similarity, question generation, and trend analytics" },
|
|
79
|
+
},
|
|
80
|
+
guidelines: {
|
|
81
|
+
pagination: "Cursor-based pagination with max 20 items per request (50 items max via direct REST)",
|
|
82
|
+
filtering: "At least one filter (subject, examType, or year) is required",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Return sitemap links for developer documentation and portal.
|
|
89
|
+
*/
|
|
90
|
+
public getSitemap() {
|
|
91
|
+
return {
|
|
92
|
+
portal: {
|
|
93
|
+
home: "https://aloc.com.ng",
|
|
94
|
+
dashboard: "https://aloc.com.ng/dashboard",
|
|
95
|
+
apiKeys: "https://aloc.com.ng/dashboard/api-keys",
|
|
96
|
+
usage: "https://aloc.com.ng/dashboard/usage",
|
|
97
|
+
billing: "https://aloc.com.ng/dashboard/billing",
|
|
98
|
+
},
|
|
99
|
+
documentation: {
|
|
100
|
+
introduction: "https://aloc.com.ng/docs/getting-started",
|
|
101
|
+
authentication: "https://aloc.com.ng/docs/authentication",
|
|
102
|
+
questions: "https://aloc.com.ng/docs/questions",
|
|
103
|
+
metadata: "https://aloc.com.ng/docs/metadata",
|
|
104
|
+
explanations: "https://aloc.com.ng/docs/explanations",
|
|
105
|
+
mcp: "https://aloc.com.ng/docs/mcp",
|
|
106
|
+
errors: "https://aloc.com.ng/docs/errors",
|
|
107
|
+
openapiSpec: `${this.baseUrl}/docs`,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Search examination questions with filters.
|
|
114
|
+
*/
|
|
38
115
|
public async getQuestions(params: {
|
|
39
116
|
subject: string;
|
|
40
117
|
examType?: string;
|
|
@@ -47,6 +124,9 @@ export class AlocClient {
|
|
|
47
124
|
return response.data;
|
|
48
125
|
}
|
|
49
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Retrieve step-by-step AI explanation.
|
|
129
|
+
*/
|
|
50
130
|
public async getQuestionExplanation(questionId: string, depth?: string) {
|
|
51
131
|
const response = await this.http.post(`/questions/${questionId}/explain`, {
|
|
52
132
|
depth: depth || "step_by_step",
|
|
@@ -54,11 +134,17 @@ export class AlocClient {
|
|
|
54
134
|
return response.data;
|
|
55
135
|
}
|
|
56
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Explore curriculum topics and taxonomy hierarchy.
|
|
139
|
+
*/
|
|
57
140
|
public async getCurriculumTaxonomy(params?: { subject?: string; examType?: string }) {
|
|
58
141
|
const response = await this.http.get("/curriculum", { params });
|
|
59
142
|
return response.data;
|
|
60
143
|
}
|
|
61
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Find vector-similar exam questions.
|
|
147
|
+
*/
|
|
62
148
|
public async getSimilarQuestions(questionId: string, limit?: number) {
|
|
63
149
|
const response = await this.http.get(`/questions/${questionId}/similar`, {
|
|
64
150
|
params: { limit: limit || 3 },
|
|
@@ -66,8 +152,52 @@ export class AlocClient {
|
|
|
66
152
|
return response.data;
|
|
67
153
|
}
|
|
68
154
|
|
|
155
|
+
/**
|
|
156
|
+
* List available subjects on ALOC.
|
|
157
|
+
*/
|
|
69
158
|
public async getSubjects() {
|
|
70
159
|
const response = await this.http.get("/subjects");
|
|
71
160
|
return response.data;
|
|
72
161
|
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* List examination years available for a specific subject.
|
|
165
|
+
*/
|
|
166
|
+
public async getSubjectYears(subject: string, examType?: string) {
|
|
167
|
+
const response = await this.http.get(`/subjects/${encodeURIComponent(subject)}/years`, {
|
|
168
|
+
params: examType ? { examType } : undefined,
|
|
169
|
+
});
|
|
170
|
+
return response.data;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check credit balance and subscription status.
|
|
175
|
+
*/
|
|
176
|
+
public async checkCreditBalance() {
|
|
177
|
+
const response = await this.http.get("/subjects");
|
|
178
|
+
const headers = response.headers;
|
|
179
|
+
return {
|
|
180
|
+
creditsRemaining: headers["x-credits-remaining"] ? parseInt(headers["x-credits-remaining"], 10) : "active",
|
|
181
|
+
creditsUsedLastRequest: headers["x-credits-used"] ? parseInt(headers["x-credits-used"], 10) : 0,
|
|
182
|
+
authenticated: true,
|
|
183
|
+
apiKeyPrefix: this.apiKey.substring(0, 10) + "••••••••",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Report an error, typo, or disputed answer on a question.
|
|
189
|
+
*/
|
|
190
|
+
public async reportQuestionIssue(
|
|
191
|
+
questionId: string,
|
|
192
|
+
type: "question" | "option_a" | "option_b" | "option_c" | "option_d" | "correct_answer" | "explanation" | "other",
|
|
193
|
+
message: string,
|
|
194
|
+
reporterName?: string
|
|
195
|
+
) {
|
|
196
|
+
const response = await this.http.post(`/questions/${questionId}/report`, {
|
|
197
|
+
type,
|
|
198
|
+
message,
|
|
199
|
+
reporterName: reporterName || "ALOC AI Agent",
|
|
200
|
+
});
|
|
201
|
+
return response.data;
|
|
202
|
+
}
|
|
73
203
|
}
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,53 @@ const server = new Server(
|
|
|
26
26
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27
27
|
return {
|
|
28
28
|
tools: [
|
|
29
|
+
{
|
|
30
|
+
name: "aloc_get_platform_overview",
|
|
31
|
+
description:
|
|
32
|
+
"Discover ALOC assessment infrastructure overview, supported African exam bodies (WAEC, JAMB, NECO), subject list, year coverage (2010–2024), and API layers. Free (0 credits).",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "aloc_get_sitemap",
|
|
40
|
+
description:
|
|
41
|
+
"Get complete navigation sitemap and direct links to ALOC developer portal, documentation, and error code guides. Free (0 credits).",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "aloc_get_subject_years",
|
|
49
|
+
description:
|
|
50
|
+
"List all available examination years for a specific subject and exam body. Free (0 credits).",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
subject: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "Subject name (e.g. 'mathematics', 'physics', 'english')",
|
|
57
|
+
},
|
|
58
|
+
examType: {
|
|
59
|
+
type: "string",
|
|
60
|
+
enum: ["waec", "jamb", "neco", "post-utme"],
|
|
61
|
+
description: "Optional examination body filter",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
required: ["subject"],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "aloc_check_credit_balance",
|
|
69
|
+
description:
|
|
70
|
+
"Check the developer's remaining ALOC API credit balance and authentication status. Free (0 credits).",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
29
76
|
{
|
|
30
77
|
name: "aloc_search_questions",
|
|
31
78
|
description:
|
|
@@ -124,6 +171,43 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
124
171
|
required: ["questionId"],
|
|
125
172
|
},
|
|
126
173
|
},
|
|
174
|
+
{
|
|
175
|
+
name: "aloc_report_question_issue",
|
|
176
|
+
description:
|
|
177
|
+
"Report a typo, incorrect answer key, or ambiguity on a specific question to ALOC quality moderation. Free (0 credits).",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
questionId: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "UUID of the question with an issue",
|
|
184
|
+
},
|
|
185
|
+
type: {
|
|
186
|
+
type: "string",
|
|
187
|
+
enum: [
|
|
188
|
+
"question",
|
|
189
|
+
"option_a",
|
|
190
|
+
"option_b",
|
|
191
|
+
"option_c",
|
|
192
|
+
"option_d",
|
|
193
|
+
"correct_answer",
|
|
194
|
+
"explanation",
|
|
195
|
+
"other",
|
|
196
|
+
],
|
|
197
|
+
description: "Category of issue identified",
|
|
198
|
+
},
|
|
199
|
+
message: {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "Detailed description of the issue or mathematical discrepancy",
|
|
202
|
+
},
|
|
203
|
+
reporterName: {
|
|
204
|
+
type: "string",
|
|
205
|
+
description: "Optional name of reporting agent or developer",
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ["questionId", "type", "message"],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
127
211
|
],
|
|
128
212
|
};
|
|
129
213
|
});
|
|
@@ -134,6 +218,55 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
134
218
|
|
|
135
219
|
try {
|
|
136
220
|
switch (name) {
|
|
221
|
+
case "aloc_get_platform_overview": {
|
|
222
|
+
const result = client.getPlatformOverview();
|
|
223
|
+
return {
|
|
224
|
+
content: [
|
|
225
|
+
{
|
|
226
|
+
type: "text",
|
|
227
|
+
text: JSON.stringify(result, null, 2),
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case "aloc_get_sitemap": {
|
|
234
|
+
const result = client.getSitemap();
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
text: JSON.stringify(result, null, 2),
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
case "aloc_get_subject_years": {
|
|
246
|
+
const { subject, examType } = (args || {}) as any;
|
|
247
|
+
const result = await client.getSubjectYears(subject, examType);
|
|
248
|
+
return {
|
|
249
|
+
content: [
|
|
250
|
+
{
|
|
251
|
+
type: "text",
|
|
252
|
+
text: JSON.stringify(result, null, 2),
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case "aloc_check_credit_balance": {
|
|
259
|
+
const result = await client.checkCreditBalance();
|
|
260
|
+
return {
|
|
261
|
+
content: [
|
|
262
|
+
{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: JSON.stringify(result, null, 2),
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
137
270
|
case "aloc_search_questions": {
|
|
138
271
|
const { subject, examType, year, topic, difficulty, limit } = (args || {}) as any;
|
|
139
272
|
const result = await client.getQuestions({
|
|
@@ -193,6 +326,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
193
326
|
};
|
|
194
327
|
}
|
|
195
328
|
|
|
329
|
+
case "aloc_report_question_issue": {
|
|
330
|
+
const { questionId, type, message, reporterName } = (args || {}) as any;
|
|
331
|
+
const result = await client.reportQuestionIssue(questionId, type, message, reporterName);
|
|
332
|
+
return {
|
|
333
|
+
content: [
|
|
334
|
+
{
|
|
335
|
+
type: "text",
|
|
336
|
+
text: JSON.stringify(result, null, 2),
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
196
342
|
default:
|
|
197
343
|
throw new Error(`Unknown ALOC tool: ${name}`);
|
|
198
344
|
}
|