@edubase/mcp 1.0.23

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 EduBase
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ <img src="https://static.edubase.net/media/brand/title/color.png" alt="EduBase logo" height="150" />
2
+
3
+ # EduBase MCP server
4
+
5
+ [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/EduBase/MCP/main.svg)](https://results.pre-commit.ci/latest/github/EduBase/MCP/main)
6
+ [![smithery badge](https://smithery.ai/badge/@EduBase/MCP)](https://smithery.ai/server/@EduBase/MCP)
7
+
8
+ This repository contains the **implementation of the Model Context Protocol** (MCP) server **for the EduBase platform**. It allows MCP clients (for example Claude Desktop) and LLMs to interact with your EduBase account and perform tasks on your behalf. It supports stdio, SSE and streamable HTTP transport protocols.
9
+
10
+ ![EduBase MCP demo GIF: Claude uploads math questions](https://shared.edubase.net/mcp/EduBaseMCPdemomath.gif)
11
+
12
+ <a href="https://glama.ai/mcp/servers/@EduBase/MCP">
13
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/@EduBase/MCP/badge" alt="EduBase Server MCP server" />
14
+ </a>
15
+
16
+ ## What is EduBase?
17
+
18
+ EduBase is an innovative, modular, online educational platform that makes learning more enjoyable, simpler and interactive, suitable for educational institutions or enterprises.
19
+
20
+ ### Why choose EduBase?
21
+
22
+ EduBase revolutionizes digital learning with its unique combination of features:
23
+
24
+ * **Advanced Quiz System** with parametrization allowing infinite variations of the same question, real-time cheating detection, beautiful LaTeX typesetting, advanced STEM-support and automatic grading
25
+ * **Unified Learning Environment** that centralizes all your educational content — videos, exams, documents, and SCORM modules — in one intuitive system
26
+ * **Enterprise-Grade Security** with features like SSO integration, fine-grained access controls, comprehensive auditing, and GDPR compliance
27
+ * **Integration** with your existing systems through LTI, comprehensive API, and custom integration options
28
+ * **AI-Assisted Tools**, such as EduBase Assistant, that can instantly transform your existing content into interactive quizzes and assessments, or translate your materials from one language to another
29
+
30
+ From higher education institutions to corporate training departments, EduBase scales to meet your specific needs while maintaining an intuitive user experience across all devices.
31
+
32
+ ### Demo video
33
+
34
+ Collaboratively creating and uploading questions, scheduling exams and analyzing user results with Claude:
35
+
36
+ <a href="https://www.youtube.com/watch?v=jvGP-5NzRPs">
37
+ <img src="https://img.youtube.com/vi/jvGP-5NzRPs/maxresdefault.jpg" alt="Demonstrating EduBase's MCP server to collaboratively create and upload questions, schedule exams and analyze results." width="600"/>
38
+ </a>
39
+
40
+ ### Obtaining your API credentials
41
+
42
+ Once logged in, on your Dashboard, search for the Integrations menu, click "add integration" and choose the type "EduBase API".
43
+
44
+ **If you don't see this option**, enter the `MCPGITHUB` activation code or feel free to contact us to request access at [info@edubase.net](mailto:info@edubase.net).
45
+
46
+ <img src="https://shared.edubase.net/mcp/EduBase_Integration_page_with_API_credentials.png" alt="EduBase API credentials page" width="500" />
47
+
48
+ ## Tools
49
+
50
+ Each documented API endpoint is available as a separate tool, named `edubase_<method>_<endpoint>`. For example, the tool for the `GET /user:me` endpoint is named `edubase_get_user_me`. See our [developer documentation](https://developer.edubase.net) for more information.
51
+
52
+ ## Configuration
53
+
54
+ The MCP server can be configured using environment variables. The following variables are available:
55
+
56
+ | Variable | Description | Required | Default value |
57
+ |---|---|---|---|
58
+ | `EDUBASE_API_URL` | The base URL of the EduBase API, most probably `https://subdomain.edubase.net/api`. | **Yes** | `https://www.edubase.net/api` |
59
+ | `EDUBASE_API_APP` | The App ID of your integration app on EduBase, the `app` on the EduBase API. Find this in the integration details window on EduBase. | Not if HTTP transport is used with authentication, otherwise **Yes** | - |
60
+ | `EDUBASE_API_KEY` | The Secret key of your integration app on EduBase, the `secret` on the EduBase API. Find this along the App ID in the integration details window on EduBase. | Not if HTTP transport is used with authentication, otherwise **Yes** | - |
61
+ | `EDUBASE_SSE_MODE` | Start MCP server in HTTP mode with SSE transport. Value must be `true`. | No | `false` |
62
+ | `EDUBASE_STREAMABLE_HTTP_MODE` | Start MCP server in HTTP mode with streamable HTTP transport. Value must be `true`. | No | `false` |
63
+ | `EDUBASE_HTTP_PORT` | HTTP server will listen on this port if SSE or streamable HTTP transport mode is used. | No | 3000 |
64
+
65
+ ## Use as a remote MCP server
66
+
67
+ You can use the **EduBase MCP server as a remote MCP server** for your MCP client. To do this, you need to host the MCP server where clients can access it, and then configure the client to connect to the server. Either start it with SSE or streamable HTTP transport mode and always use HTTPS when accessing the server remotely over the internet!
68
+
69
+ ### Authentication with remote servers
70
+
71
+ You can use server in two modes:
72
+
73
+ * **Without client authentication**: In this mode, the server will not require any authentication from the client. This is useful for testing or development purposes, or in a closed network but it is not recommended for production use. For this, you have to configure the server with the `EDUBASE_API_APP` and `EDUBASE_API_KEY` as well!
74
+ * **With Bearer token authentication**: In this mode, the server will require a Bearer token to be sent with each request. This is the recommended way to use the server in production. You can obtain the Bearer token from your EduBase account by creating an integration app and providing the App ID and Secret key in the `{app}:{secret}` format, base64 encoded as a token. The server will then use this token to authenticate the client and authorize access to the API endpoints.
75
+
76
+ ## Usage with Claude Desktop
77
+
78
+ For a step-by-step walkthrough, see our blog post on how to [connect EduBase with Claude: The Complete MCP Integration Guide](https://edubase.blog/claude-mcp-integration-guide/).
79
+
80
+ ### Installing manually
81
+
82
+ Add the following to your `claude_desktop_config.json`:
83
+
84
+ #### Using Node.js
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**!
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "edubase": {
92
+ "command": "node",
93
+ "args": [
94
+ "/path/to/dist/index.js"
95
+ ],
96
+ "env": {
97
+ "EDUBASE_API_URL": "https://domain.edubase.net/api",
98
+ "EDUBASE_API_APP": "your_integration_app_id",
99
+ "EDUBASE_API_KEY": "your_integration_secret_key"
100
+ }
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ #### Using Docker
107
+
108
+ Before running the MCP server, make sure you have **Docker installed and is running**. You can download it from [docker.com](https://www.docker.com/) or use a package manager. Do not forget to **configure the environmental variables**!
109
+
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "edubase": {
114
+ "command": "docker",
115
+ "args": [
116
+ "run",
117
+ "-i",
118
+ "--rm",
119
+ "-e",
120
+ "EDUBASE_API_URL",
121
+ "-e",
122
+ "EDUBASE_API_APP",
123
+ "-e",
124
+ "EDUBASE_API_KEY",
125
+ "edubase/mcp"
126
+ ],
127
+ "env": {
128
+ "EDUBASE_API_URL": "https://domain.edubase.net/api",
129
+ "EDUBASE_API_APP": "your_integration_app_id",
130
+ "EDUBASE_API_KEY": "your_integration_secret_key"
131
+ }
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ ### Installing via remote MCP server
138
+
139
+ You can use the provided EduBase MCP server (if available) as a remote server. We recommend Base64 encoding your `EDUBASE_API_APP` and `EDUBASE_API_KEY` and using it in as a Bearer token in the `Authorization` header (`Authorization: Bearer ${BASE64_ENCODED_TOKEN}`).
140
+
141
+ ```json
142
+ {
143
+ "mcpServers": {
144
+ "edubase": {
145
+ "command": "npx",
146
+ "args": [
147
+ "mcp-remote",
148
+ "https://domain.edubase.net/mcp",
149
+ "--header",
150
+ "Authorization: Bearer ${EDUBASE_API_APP}:${EDUBASE_API_KEY}"
151
+ ]
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ ### Installing via Smithery
158
+
159
+ To install EduBase MCP server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@EduBase/MCP):
160
+
161
+ ```bash
162
+ npx -y @smithery/cli install @EduBase/MCP --client claude
163
+ ```
164
+
165
+ ## Contact
166
+
167
+ Website: [www.edubase.net](www.edubase.net)
168
+ Developer Documentation: [developer.edubase.net](developer.edubase.net)
169
+ Email: [info@edubase.net](mailto:info@edubase.net)
@@ -0,0 +1,4 @@
1
+ /* Get request client IP address */
2
+ export function getClientIp(req) {
3
+ return (req.get('x-forwarded-for')?.split(',')[0] || req.get('x-real-ip') || req.ip || req.socket.remoteAddress || null);
4
+ }
package/dist/index.js ADDED
@@ -0,0 +1,430 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
5
+ import { randomUUID } from "node:crypto";
6
+ import queryString from "query-string";
7
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
8
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
+ import { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js';
10
+ import express from "express";
11
+ import bodyParser from "body-parser";
12
+ import { getClientIp } from "./helpers.js";
13
+ /* Enable SSE or Streamable HTTP mode */
14
+ const SSE = ((process.env.EDUBASE_SSE_MODE || 'false') == 'true');
15
+ const STREAMABLE_HTTP = ((process.env.EDUBASE_STREAMABLE_HTTP_MODE || 'false') == 'true');
16
+ /* Check required EduBase environment variables */
17
+ const EDUBASE_API_URL = process.env.EDUBASE_API_URL || 'https://www.edubase.net/api';
18
+ if (!SSE && !STREAMABLE_HTTP && EDUBASE_API_URL.length == 0) {
19
+ console.error('Error: EDUBASE_API_URL environment variable is required with this transport mode');
20
+ process.exit(1);
21
+ }
22
+ const EDUBASE_API_APP = process.env.EDUBASE_API_APP || '';
23
+ if (!SSE && !STREAMABLE_HTTP && EDUBASE_API_APP.length == 0) {
24
+ console.error('Error: EDUBASE_API_APP environment variable is required with this transport mode');
25
+ process.exit(1);
26
+ }
27
+ const EDUBASE_API_KEY = process.env.EDUBASE_API_KEY || '';
28
+ if (!SSE && !STREAMABLE_HTTP && EDUBASE_API_KEY.length == 0) {
29
+ console.error('Error: EDUBASE_API_KEY environment variable is required with this transport mode');
30
+ process.exit(1);
31
+ }
32
+ /* 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
+ });
45
+ /* EduBase API rate limits (via environment variables or configured defaults) */
46
+ const EDUBASE_API_MAXRATE_DEFAULT = {
47
+ second: 10,
48
+ minute: 1000
49
+ };
50
+ const EDUBASE_API_MAXRATE_ENV = {
51
+ second: parseInt(process.env.EDUBASE_API_MAXRATE || ''),
52
+ minute: parseInt(process.env.EDUBASE_API_MAXRATE60 || '')
53
+ };
54
+ const EDUBASE_API_MAXRATE = {
55
+ second: Number.isInteger(EDUBASE_API_MAXRATE_ENV.second) ? EDUBASE_API_MAXRATE_ENV.second : EDUBASE_API_MAXRATE_DEFAULT.second,
56
+ minute: Number.isInteger(EDUBASE_API_MAXRATE_ENV.minute) ? EDUBASE_API_MAXRATE_ENV.minute : EDUBASE_API_MAXRATE_DEFAULT.minute,
57
+ };
58
+ let requestRate = {
59
+ second: 0,
60
+ minute: 0,
61
+ since: {
62
+ second: Date.now(),
63
+ minute: Date.now()
64
+ }
65
+ };
66
+ function checkRateLimit() {
67
+ const now = Date.now();
68
+ if (now - requestRate.since.second > 1000) {
69
+ /* New second, reset rate */
70
+ requestRate.second = 0;
71
+ requestRate.since.second = now;
72
+ }
73
+ if (now - requestRate.since.minute > 60000) {
74
+ /* New minute, reset rate */
75
+ requestRate.minute = 0;
76
+ requestRate.since.minute = now;
77
+ }
78
+ if (requestRate.second >= EDUBASE_API_MAXRATE.second || requestRate.minute >= EDUBASE_API_MAXRATE.minute) {
79
+ throw new Error('Rate limit exceeded');
80
+ }
81
+ requestRate.second++;
82
+ requestRate.minute++;
83
+ }
84
+ async function sendEduBaseApiRequest(method, endpoint, data, authentication) {
85
+ /* Check method and endpoint */
86
+ method = method.toUpperCase();
87
+ if (!['GET', 'POST', 'DELETE'].includes(method)) {
88
+ throw new Error('Invalid method: "' + method + '"');
89
+ }
90
+ if (endpoint.length == 0) {
91
+ throw new Error('Invalid endpoint');
92
+ }
93
+ /* Check rate limit */
94
+ checkRateLimit();
95
+ /* Prepare authentication (prefer EDUBASE_API_APP and EDUBASE_API_KEY environment variables) */
96
+ if (!authentication) {
97
+ authentication = { app: EDUBASE_API_APP, secret: EDUBASE_API_KEY };
98
+ }
99
+ else {
100
+ if (!authentication.hasOwnProperty('app') || authentication.app.length == 0 || EDUBASE_API_APP.length > 0) {
101
+ authentication.app = EDUBASE_API_APP;
102
+ }
103
+ if (!authentication.hasOwnProperty('secret') || authentication.secret.length == 0 || EDUBASE_API_KEY.length > 0) {
104
+ authentication.secret = EDUBASE_API_KEY;
105
+ }
106
+ }
107
+ /* Send request with input data */
108
+ let headers = {
109
+ 'Content-Type': 'application/json',
110
+ 'Accept-Encoding': 'gzip',
111
+ 'EduBase-API-Client': 'MCP',
112
+ 'EduBase-API-Transport': (STREAMABLE_HTTP) ? 'Streamable HTTP' : ((SSE) ? 'SSE' : 'Stdio'),
113
+ 'EduBase-API-App': authentication.app,
114
+ 'EduBase-API-Secret': authentication.secret
115
+ };
116
+ const response = await fetch(endpoint + (method == 'GET' ? '?' + queryString.stringify(data) : ''), {
117
+ method: method,
118
+ body: (method != 'GET' ? JSON.stringify(data) : undefined),
119
+ headers: headers
120
+ });
121
+ if (!response.ok) {
122
+ throw new Error(`EduBase API error: ${response.status} ${response.statusText}` + (response.headers.has('EduBase-API-Error') ? ` (${response.headers.get('EduBase-API-Error')})` : ''));
123
+ }
124
+ /* Parse response and return as object */
125
+ let clonedResponse = response.clone();
126
+ try {
127
+ /* First try to decode as JSON */
128
+ return await response.json();
129
+ }
130
+ catch (error) {
131
+ /* Response might be empty string with a 200 status code */
132
+ return await clonedResponse.text();
133
+ }
134
+ }
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
+ /* Start MCP server */
237
+ if (STREAMABLE_HTTP) {
238
+ /* Using HTTP with Streamable HTTP transport */
239
+ const app = express();
240
+ app.disable('x-powered-by');
241
+ app.use(bodyParser.json());
242
+ const transports = {};
243
+ app.post('/mcp', async (req, res) => {
244
+ /* Handle POST requests */
245
+ const sessionId = req.headers['mcp-session-id'];
246
+ let transport;
247
+ if (sessionId && transports[sessionId]) {
248
+ /* Use existing session */
249
+ transport = transports[sessionId];
250
+ }
251
+ else if (!sessionId && isInitializeRequest(req.body)) {
252
+ /* New session */
253
+ const eventStore = new InMemoryEventStore();
254
+ transport = new StreamableHTTPServerTransport({
255
+ sessionIdGenerator: () => randomUUID(),
256
+ eventStore,
257
+ onsessioninitialized: (sessionId) => {
258
+ transports[sessionId] = transport;
259
+ }
260
+ });
261
+ transport.onclose = () => {
262
+ if (transport.sessionId) {
263
+ delete transports[transport.sessionId];
264
+ }
265
+ };
266
+ await server.connect(transport);
267
+ }
268
+ else {
269
+ console.error("No session found for request");
270
+ res.status(400).send();
271
+ return;
272
+ }
273
+ try {
274
+ let override = { EDUBASE_API_URL: null, EDUBASE_API_APP: null, EDUBASE_API_KEY: null };
275
+ if (req.query?.config && typeof req.query.config == 'string') {
276
+ /* Apply Smithery configuration */
277
+ const smitheryConfig = JSON.parse(Buffer.from(req.query.config, 'base64').toString());
278
+ if (smitheryConfig.edubaseApiUrl && typeof smitheryConfig.edubaseApiUrl == 'string' && smitheryConfig.edubaseApiUrl.length > 0) {
279
+ override.EDUBASE_API_URL = smitheryConfig.edubaseApiUrl;
280
+ }
281
+ if (smitheryConfig.edubaseApiApp && typeof smitheryConfig.edubaseApiApp == 'string' && smitheryConfig.edubaseApiApp.length > 0) {
282
+ override.EDUBASE_API_APP = smitheryConfig.edubaseApiApp;
283
+ }
284
+ if (smitheryConfig.edubaseApiKey && typeof smitheryConfig.edubaseApiKey == 'string' && smitheryConfig.edubaseApiKey.length > 0) {
285
+ override.EDUBASE_API_KEY = smitheryConfig.edubaseApiKey;
286
+ }
287
+ }
288
+ const params = req.body?.params || {};
289
+ params._meta = {
290
+ ip: getClientIp(req),
291
+ headers: req.headers,
292
+ override: override,
293
+ };
294
+ await transport.handleRequest(req, res, { ...req.body, params });
295
+ }
296
+ catch (error) {
297
+ console.error("Error handling POST request for session (" + sessionId + "): " + error);
298
+ res.status(500).send();
299
+ }
300
+ });
301
+ app.get('/mcp', async (req, res) => {
302
+ /* Handle GET requests */
303
+ const sessionId = req.headers['mcp-session-id'];
304
+ if (!sessionId || !transports[sessionId]) {
305
+ res.status(400).send('Invalid session ID');
306
+ return;
307
+ }
308
+ try {
309
+ const transport = transports[sessionId];
310
+ await transport.handleRequest(req, res);
311
+ }
312
+ catch (error) {
313
+ console.error("Error handling GET request for session (" + sessionId + "): " + error);
314
+ res.status(500).send();
315
+ }
316
+ });
317
+ app.delete('/mcp', async (req, res) => {
318
+ /* Handle DELETE requests */
319
+ const sessionId = req.headers['mcp-session-id'];
320
+ if (!sessionId || !transports[sessionId]) {
321
+ res.status(400).send('Invalid session ID');
322
+ return;
323
+ }
324
+ try {
325
+ const transport = transports[sessionId];
326
+ await transport.handleRequest(req, res);
327
+ }
328
+ catch (error) {
329
+ console.error("Error handling DELETE request for session (" + sessionId + "): " + error);
330
+ res.status(500).send();
331
+ }
332
+ });
333
+ app.get('/health', async (_, res) => {
334
+ /* Health check endpoint */
335
+ res.status(200).send();
336
+ });
337
+ const EDUBASE_HTTP_PORT = parseInt(process.env.EDUBASE_HTTP_PORT || process.env.PORT || '3000');
338
+ app.listen(EDUBASE_HTTP_PORT, () => {
339
+ console.error("EduBase MCP server is now listening on HTTP port " + EDUBASE_HTTP_PORT + " with Streamable HTTP transport");
340
+ });
341
+ process.on('SIGTERM', () => {
342
+ /* Graceful shutdown */
343
+ console.error("Received SIGTERM, shutting down EduBase MCP server...");
344
+ server.close();
345
+ });
346
+ }
347
+ else if (SSE) {
348
+ /* Using HTTP with SSE transport */
349
+ const app = express();
350
+ app.use(bodyParser.json());
351
+ app.disable('x-powered-by');
352
+ const transports = {};
353
+ app.get('/sse', async (_, res) => {
354
+ /* Handle SSE sessions */
355
+ const transport = new SSEServerTransport('/messages', res);
356
+ transports[transport.sessionId] = transport;
357
+ res.on('close', () => {
358
+ delete transports[transport.sessionId];
359
+ });
360
+ try {
361
+ await server.connect(transport);
362
+ }
363
+ catch (error) {
364
+ console.error("Error connecting transport to MCP server for session (" + transport.sessionId + "): " + error);
365
+ }
366
+ });
367
+ app.post('/messages', async (req, res) => {
368
+ /* Handle MCP messages */
369
+ const sessionId = req.query.sessionId;
370
+ const transport = transports[sessionId] ?? Object.values(transports)[0];
371
+ if (transport) {
372
+ try {
373
+ let override = { EDUBASE_API_URL: null, EDUBASE_API_APP: null, EDUBASE_API_KEY: null };
374
+ if (req.query?.config && typeof req.query.config == 'string') {
375
+ /* Apply Smithery configuration */
376
+ const smitheryConfig = JSON.parse(Buffer.from(req.query.config, 'base64').toString());
377
+ if (smitheryConfig.edubaseApiUrl && typeof smitheryConfig.edubaseApiUrl == 'string' && smitheryConfig.edubaseApiUrl.length > 0) {
378
+ override.EDUBASE_API_URL = smitheryConfig.edubaseApiUrl;
379
+ }
380
+ if (smitheryConfig.edubaseApiApp && typeof smitheryConfig.edubaseApiApp == 'string' && smitheryConfig.edubaseApiApp.length > 0) {
381
+ override.EDUBASE_API_APP = smitheryConfig.edubaseApiApp;
382
+ }
383
+ if (smitheryConfig.edubaseApiKey && typeof smitheryConfig.edubaseApiKey == 'string' && smitheryConfig.edubaseApiKey.length > 0) {
384
+ override.EDUBASE_API_KEY = smitheryConfig.edubaseApiKey;
385
+ }
386
+ }
387
+ const params = req.body?.params || {};
388
+ params._meta = {
389
+ ip: getClientIp(req),
390
+ headers: req.headers,
391
+ override: override,
392
+ };
393
+ await transport.handlePostMessage(req, res, { ...req.body, params });
394
+ }
395
+ catch (error) {
396
+ console.error("Error handling message for session (" + sessionId + "): " + error);
397
+ res.status(500).send();
398
+ }
399
+ }
400
+ else {
401
+ console.error("No transport found for session (" + sessionId + ")");
402
+ res.status(400).send();
403
+ }
404
+ });
405
+ app.get('/health', async (_, res) => {
406
+ /* Health check endpoint */
407
+ res.status(200).send();
408
+ });
409
+ const EDUBASE_HTTP_PORT = parseInt(process.env.EDUBASE_HTTP_PORT || process.env.PORT || '3000');
410
+ app.listen(EDUBASE_HTTP_PORT, () => {
411
+ console.error("EduBase MCP server is now listening on HTTP port " + EDUBASE_HTTP_PORT + " with SSE transport");
412
+ });
413
+ process.on('SIGTERM', () => {
414
+ /* Graceful shutdown */
415
+ console.error("Received SIGTERM, shutting down EduBase MCP server...");
416
+ server.close();
417
+ });
418
+ }
419
+ else {
420
+ /* Using stdio transport */
421
+ async function runMcpServer() {
422
+ const transport = new StdioServerTransport();
423
+ await server.connect(transport);
424
+ console.error("EduBase MCP server is now listening on standard input/output");
425
+ }
426
+ runMcpServer().catch((error) => {
427
+ console.error("Cannot start EduBase MCP server: ", error);
428
+ process.exit(1);
429
+ });
430
+ }
@@ -0,0 +1,4 @@
1
+ /* Prompt definitions */
2
+ export const EDUBASE_API_PROMPTS = {};
3
+ /* Prompt handler definitions */
4
+ export const EDUBASE_API_PROMPTS_HANDLERS = {};