@edubase/mcp 1.0.23 → 1.1.1

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 CHANGED
@@ -83,7 +83,28 @@ Add the following to your `claude_desktop_config.json`:
83
83
 
84
84
  #### Using Node.js
85
85
 
86
- Before running the MCP server, make sure you have **Node.js installed**. You can download it from [nodejs.org](https://nodejs.org/) or use a package manager like `brew`. Download EduBase MCP server release or clone the repository and run `npm run build` to build the server. Do not forget to adjust `/path/to/dist` to the actual directory and **configure the environmental variables**!
86
+ Before running the MCP server, make sure you have **Node.js installed**. You can download it from [nodejs.org](https://nodejs.org/) or use a package manager like `brew`.
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "edubase": {
92
+ "command": "npx",
93
+ "args": [
94
+ "-y",
95
+ "@edubase/mcp"
96
+ ],
97
+ "env": {
98
+ "EDUBASE_API_URL": "https://domain.edubase.net/api",
99
+ "EDUBASE_API_APP": "your_integration_app_id",
100
+ "EDUBASE_API_KEY": "your_integration_secret_key"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ Or download EduBase MCP server release or clone the repository and run `npm run build` to build the server. Do not forget to adjust `/path/to/dist` to the actual directory and **configure the environmental variables**!
87
108
 
88
109
  ```json
89
110
  {
package/dist/helpers.js CHANGED
@@ -2,3 +2,12 @@
2
2
  export function getClientIp(req) {
3
3
  return (req.get('x-forwarded-for')?.split(',')[0] || req.get('x-real-ip') || req.ip || req.socket.remoteAddress || null);
4
4
  }
5
+ /* Get header value */
6
+ export function getHeaderValue(req, name) {
7
+ const raw = req.headers?.[name.toLowerCase()];
8
+ if (typeof raw === "string" && raw.length > 0)
9
+ return raw;
10
+ if (Array.isArray(raw) && raw.length > 0 && typeof raw[0] === "string")
11
+ return raw[0];
12
+ return null;
13
+ }
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
4
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import queryString from "query-string";
7
7
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
@@ -9,7 +9,11 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
9
9
  import { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js';
10
10
  import express from "express";
11
11
  import bodyParser from "body-parser";
12
- import { getClientIp } from "./helpers.js";
12
+ import { getClientIp, getHeaderValue } from "./helpers.js";
13
+ import packageJson from '../package.json' with { type: "json" };
14
+ import * as z from 'zod/v4';
15
+ /* Version */
16
+ const VERSION = packageJson.version;
13
17
  /* Enable SSE or Streamable HTTP mode */
14
18
  const SSE = ((process.env.EDUBASE_SSE_MODE || 'false') == 'true');
15
19
  const STREAMABLE_HTTP = ((process.env.EDUBASE_STREAMABLE_HTTP_MODE || 'false') == 'true');
@@ -30,18 +34,141 @@ if (!SSE && !STREAMABLE_HTTP && EDUBASE_API_KEY.length == 0) {
30
34
  process.exit(1);
31
35
  }
32
36
  /* Supported tools and prompts */
33
- import { EDUBASE_API_TOOLS, EDUBASE_API_TOOLS_OUTPUT_SCHEMA } from "./tools.js";
34
- import { EDUBASE_API_PROMPTS, EDUBASE_API_PROMPTS_HANDLERS } from "./prompts.js";
35
- /* Create MCP server */
36
- const server = new Server({
37
- name: '@edubase/mcp',
38
- version: '1.0.23',
39
- }, {
40
- capabilities: {
41
- prompts: {},
42
- tools: {},
43
- },
44
- });
37
+ import { EDUBASE_API_TOOLS } from "./tools.js";
38
+ import { EDUBASE_API_PROMPTS } from "./prompts.js";
39
+ /* Create MCP server with appropriate EduBase configuration */
40
+ function getEduBaseApiUrl(req) {
41
+ if (req.query?.config && typeof req.query.config == 'string') {
42
+ /* Use URL from Smithery configuration */
43
+ const smitheryConfig = JSON.parse(Buffer.from(req.query.config, 'base64').toString());
44
+ if (smitheryConfig.edubaseApiUrl && typeof smitheryConfig.edubaseApiUrl == 'string' && smitheryConfig.edubaseApiUrl.length > 0) {
45
+ return smitheryConfig.edubaseApiUrl;
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ function getEduBaseAuthentication(req) {
51
+ let EDUBASE_API_APP = null;
52
+ let EDUBASE_API_KEY = null;
53
+ if (req.query?.config && typeof req.query.config == 'string') {
54
+ /* Use authentication from Smithery configuration */
55
+ const smitheryConfig = JSON.parse(Buffer.from(req.query.config, 'base64').toString());
56
+ if (smitheryConfig.edubaseApiApp && typeof smitheryConfig.edubaseApiApp == 'string' && smitheryConfig.edubaseApiApp.length > 0) {
57
+ EDUBASE_API_APP = smitheryConfig.edubaseApiApp;
58
+ }
59
+ if (smitheryConfig.edubaseApiKey && typeof smitheryConfig.edubaseApiKey == 'string' && smitheryConfig.edubaseApiKey.length > 0) {
60
+ EDUBASE_API_KEY = smitheryConfig.edubaseApiKey;
61
+ }
62
+ }
63
+ else if (getHeaderValue(req, 'edubase-api-app') && getHeaderValue(req, 'edubase-api-secret')) {
64
+ /* Use authentication from request headers */
65
+ EDUBASE_API_APP = getHeaderValue(req, 'edubase-api-app');
66
+ EDUBASE_API_KEY = getHeaderValue(req, 'edubase-api-secret');
67
+ }
68
+ else if (getHeaderValue(req, 'authorization') && getHeaderValue(req, 'authorization').startsWith('Bearer ')) {
69
+ /* Use authentication from Bearer token */
70
+ try {
71
+ /* Decode Bearer token */
72
+ const [app, secret] = atob(getHeaderValue(req, 'authorization').split(' ')[1]).split(':');
73
+ if (app && app.length > 0 && secret && secret.length > 0) {
74
+ EDUBASE_API_APP = app;
75
+ EDUBASE_API_KEY = secret;
76
+ }
77
+ }
78
+ catch (error) {
79
+ /* Probably not encoded as base64 */
80
+ const [app, secret] = getHeaderValue(req, 'authorization').split(' ')[1].split(':');
81
+ if (app && app.length > 0 && secret && secret.length > 0) {
82
+ EDUBASE_API_APP = app;
83
+ EDUBASE_API_KEY = secret;
84
+ }
85
+ }
86
+ }
87
+ if (EDUBASE_API_APP && EDUBASE_API_KEY) {
88
+ return { app: EDUBASE_API_APP, secret: EDUBASE_API_KEY };
89
+ }
90
+ return null;
91
+ }
92
+ function createMcpServer(apiUrl = null, authentication = null) {
93
+ /* Create MCP server instance */
94
+ const server = new McpServer({
95
+ name: '@edubase/mcp',
96
+ version: VERSION,
97
+ }, {
98
+ capabilities: {
99
+ prompts: {},
100
+ tools: {},
101
+ },
102
+ });
103
+ /* Configure request handlers */
104
+ Object.values(EDUBASE_API_PROMPTS).forEach((prompt) => {
105
+ /* Register prompts */
106
+ server.registerPrompt(prompt.name, { description: prompt.description, argsSchema: prompt.argsSchema }, prompt.handler);
107
+ });
108
+ server.registerTool('edubase_mcp_server_version', { description: 'Get the MCP server version (only use for debugging)' }, async () => {
109
+ /* Static response with server version, useful for testing connectivity and authentication */
110
+ return {
111
+ content: [{ type: 'text', text: VERSION }],
112
+ isError: false,
113
+ };
114
+ });
115
+ server.registerTool('edubase_mcp_server_api', { description: 'Get the MCP server API URL (only use for debugging)' }, async () => {
116
+ /* Static response with server API URL, useful for testing connectivity and authentication */
117
+ return {
118
+ content: [{ type: 'text', text: apiUrl || EDUBASE_API_URL }],
119
+ isError: false,
120
+ };
121
+ });
122
+ Object.values(EDUBASE_API_TOOLS).forEach((tool) => {
123
+ /* Register tools */
124
+ server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema, outputSchema: tool.outputSchema }, async (args, ctx) => {
125
+ try {
126
+ const name = tool.name;
127
+ /* Decompose request and check arguments */
128
+ if (!name.match(/^edubase_(get|post|delete)/)) {
129
+ throw new Error('Invalid tool configuration');
130
+ }
131
+ if (!args) {
132
+ throw new Error('No arguments provided');
133
+ }
134
+ /* Prepare and send API request */
135
+ const [, method, ...endpoint] = name.split('_');
136
+ const response = await sendEduBaseApiRequest(method, (apiUrl || EDUBASE_API_URL) + '/' + endpoint.join(':'), args, authentication);
137
+ /* Return response */
138
+ if (response.length == 0 || tool.outputSchema === z.object({}).optional()) {
139
+ /* Endpoint without response */
140
+ return {
141
+ content: [{ type: 'text', text: 'Success.' }],
142
+ isError: false,
143
+ };
144
+ }
145
+ else if (typeof response != 'object') {
146
+ /* Response should be an object at this point */
147
+ throw new Error('Invalid response');
148
+ }
149
+ else {
150
+ /* Return response with schema */
151
+ return {
152
+ content: [{ type: 'text', text: JSON.stringify(response) }],
153
+ structuredContent: response,
154
+ isError: false,
155
+ };
156
+ }
157
+ }
158
+ catch (error) {
159
+ /* Request failed */
160
+ return {
161
+ content: [{
162
+ type: 'text',
163
+ text: `${error instanceof Error ? error.message : String(error)}`,
164
+ }],
165
+ isError: true,
166
+ };
167
+ }
168
+ });
169
+ });
170
+ return server;
171
+ }
45
172
  /* EduBase API rate limits (via environment variables or configured defaults) */
46
173
  const EDUBASE_API_MAXRATE_DEFAULT = {
47
174
  second: 10,
@@ -132,107 +259,6 @@ async function sendEduBaseApiRequest(method, endpoint, data, authentication) {
132
259
  return await clonedResponse.text();
133
260
  }
134
261
  }
135
- server.setRequestHandler(ListPromptsRequestSchema, async () => ({
136
- prompts: Object.values(EDUBASE_API_PROMPTS),
137
- }));
138
- server.setRequestHandler(GetPromptRequestSchema, (request) => {
139
- try {
140
- /* Decompose request and check arguments */
141
- const { name, arguments: args } = request.params;
142
- const promptHandler = EDUBASE_API_PROMPTS_HANDLERS[name];
143
- if (!promptHandler) {
144
- throw new Error('Prompt not found');
145
- }
146
- /* Return prompt response */
147
- return promptHandler;
148
- }
149
- catch (error) {
150
- /* Request failed */
151
- return {};
152
- }
153
- });
154
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
155
- tools: EDUBASE_API_TOOLS,
156
- }));
157
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
158
- try {
159
- /* Decompose request and check arguments */
160
- const { name, arguments: args } = request.params;
161
- if (!name.match(/^edubase_(get|post|delete)/)) {
162
- throw new Error('Invalid tool configuration');
163
- }
164
- if (!args) {
165
- throw new Error('No arguments provided');
166
- }
167
- const meta = request.params._meta || {};
168
- /* Prepare authentication */
169
- let authentication = null;
170
- if (meta && meta.override && meta.override.EDUBASE_API_APP && meta.override.EDUBASE_API_KEY) {
171
- /* Use authentication from custom configuration */
172
- authentication = {
173
- app: meta.override.EDUBASE_API_APP,
174
- secret: meta.override.EDUBASE_API_KEY
175
- };
176
- }
177
- else if (meta && meta.headers && meta.headers['edubase-api-app'] && meta.headers['edubase-api-secret']) {
178
- /* Use authentication from request headers */
179
- authentication = {
180
- app: meta.headers['edubase-api-app'],
181
- secret: meta.headers['edubase-api-secret']
182
- };
183
- }
184
- else if (meta && meta.headers && meta.headers['authorization'] && meta.headers['authorization'].startsWith('Bearer ')) {
185
- /* Use authentication from Bearer token */
186
- try {
187
- /* Decode Bearer token */
188
- const [app, secret] = atob(meta.headers['authorization'].split(' ')[1]).split(':');
189
- if (app && app.length > 0 && secret && secret.length > 0) {
190
- authentication = { app, secret };
191
- }
192
- }
193
- catch (error) {
194
- /* Probably not encoded as base64 */
195
- const [app, secret] = meta.headers['authorization'].split(' ')[1].split(':');
196
- if (app && app.length > 0 && secret && secret.length > 0) {
197
- authentication = { app, secret };
198
- }
199
- }
200
- }
201
- /* Prepare and send API request */
202
- const [, method, ...endpoint] = name.split('_');
203
- const response = await sendEduBaseApiRequest(method, (meta?.override?.EDUBASE_API_URL || EDUBASE_API_URL) + '/' + endpoint.join(':'), args, authentication);
204
- /* Return response */
205
- const outputSchemaKey = name;
206
- if (typeof EDUBASE_API_TOOLS_OUTPUT_SCHEMA[outputSchemaKey] == 'object' && Object.keys(EDUBASE_API_TOOLS_OUTPUT_SCHEMA[outputSchemaKey]).length == 0 && typeof response == 'string' && response.length == 0) {
207
- /* Endpoint without response */
208
- return {
209
- content: [{ type: 'text', text: 'Success.' }],
210
- isError: false,
211
- };
212
- }
213
- else if (typeof response != 'object') {
214
- /* Response should be an object at this point */
215
- throw new Error('Invalid response');
216
- }
217
- else {
218
- /* Return response with optional schema */
219
- return {
220
- content: [{ type: 'text', text: "Response: " + JSON.stringify(response) + (Object.keys(EDUBASE_API_TOOLS_OUTPUT_SCHEMA[outputSchemaKey]).length > 0 ? "\nResponse schema: " + JSON.stringify(EDUBASE_API_TOOLS_OUTPUT_SCHEMA[outputSchemaKey]) : '') }],
221
- isError: false,
222
- };
223
- }
224
- }
225
- catch (error) {
226
- /* Request failed */
227
- return {
228
- content: [{
229
- type: 'text',
230
- text: `${error instanceof Error ? error.message : String(error)}`,
231
- }],
232
- isError: true,
233
- };
234
- }
235
- });
236
262
  /* Start MCP server */
237
263
  if (STREAMABLE_HTTP) {
238
264
  /* Using HTTP with Streamable HTTP transport */
@@ -263,6 +289,7 @@ if (STREAMABLE_HTTP) {
263
289
  delete transports[transport.sessionId];
264
290
  }
265
291
  };
292
+ const server = createMcpServer(getEduBaseApiUrl(req), getEduBaseAuthentication(req));
266
293
  await server.connect(transport);
267
294
  }
268
295
  else {
@@ -341,7 +368,6 @@ if (STREAMABLE_HTTP) {
341
368
  process.on('SIGTERM', () => {
342
369
  /* Graceful shutdown */
343
370
  console.error("Received SIGTERM, shutting down EduBase MCP server...");
344
- server.close();
345
371
  });
346
372
  }
347
373
  else if (SSE) {
@@ -350,14 +376,15 @@ else if (SSE) {
350
376
  app.use(bodyParser.json());
351
377
  app.disable('x-powered-by');
352
378
  const transports = {};
353
- app.get('/sse', async (_, res) => {
354
- /* Handle SSE sessions */
379
+ app.get('/sse', async (req, res) => {
380
+ /* Handle SSE sessions (but prefer Streamable HTTP) */
355
381
  const transport = new SSEServerTransport('/messages', res);
356
382
  transports[transport.sessionId] = transport;
357
383
  res.on('close', () => {
358
384
  delete transports[transport.sessionId];
359
385
  });
360
386
  try {
387
+ const server = createMcpServer(getEduBaseApiUrl(req), getEduBaseAuthentication(req));
361
388
  await server.connect(transport);
362
389
  }
363
390
  catch (error) {
@@ -413,13 +440,13 @@ else if (SSE) {
413
440
  process.on('SIGTERM', () => {
414
441
  /* Graceful shutdown */
415
442
  console.error("Received SIGTERM, shutting down EduBase MCP server...");
416
- server.close();
417
443
  });
418
444
  }
419
445
  else {
420
446
  /* Using stdio transport */
421
447
  async function runMcpServer() {
422
448
  const transport = new StdioServerTransport();
449
+ const server = createMcpServer();
423
450
  await server.connect(transport);
424
451
  console.error("EduBase MCP server is now listening on standard input/output");
425
452
  }
@@ -0,0 +1,24 @@
1
+ import * as z from 'zod/v4';
2
+ /* Prompts definitions */
3
+ export const EDUBASE_API_PROMPTS_QUESTIONS = [
4
+ // Create a new question
5
+ {
6
+ name: 'edubase_prompt_create_question',
7
+ description: 'Create a new question with the given subject and content.',
8
+ argsSchema: z.object({
9
+ type: z.string().describe('The type of the question, use the EduBase question types!'),
10
+ subject: z.string().describe('The subject of the question, e.g. "Mathematics", "History", etc.')
11
+ }),
12
+ handler: async (args) => {
13
+ return {
14
+ messages: [{
15
+ role: 'user',
16
+ content: {
17
+ type: 'text',
18
+ text: `Create a new question of type ${args.type} about ${args.subject}. Please provide the question in a clear and concise manner, suitable for educational purposes.`
19
+ }
20
+ }]
21
+ };
22
+ }
23
+ }
24
+ ];
package/dist/prompts.js CHANGED
@@ -1,4 +1,5 @@
1
+ import { EDUBASE_API_PROMPTS_QUESTIONS } from "./prompts/questions.js";
1
2
  /* Prompt definitions */
2
- export const EDUBASE_API_PROMPTS = {};
3
- /* Prompt handler definitions */
4
- export const EDUBASE_API_PROMPTS_HANDLERS = {};
3
+ export const EDUBASE_API_PROMPTS = {
4
+ ...EDUBASE_API_PROMPTS_QUESTIONS,
5
+ };