@lyrra/mcp-server 1.1.3 → 1.1.7

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.
Files changed (108) hide show
  1. package/README.md +80 -250
  2. package/dist/auth-session.js +171 -0
  3. package/dist/eduflow-block-docs.js +438 -0
  4. package/dist/http-incoming-auth.js +48 -0
  5. package/dist/http-main.js +104 -0
  6. package/dist/index.js +16 -12
  7. package/dist/lyrra-http.js +80 -0
  8. package/dist/lyrra-mcp-core.js +174 -0
  9. package/dist/openapi-parse.js +61 -0
  10. package/dist/register-eduflow-block-tools.js +31 -0
  11. package/package.json +41 -13
  12. package/Dockerfile +0 -16
  13. package/dist/client.d.ts +0 -23
  14. package/dist/client.d.ts.map +0 -1
  15. package/dist/client.js +0 -92
  16. package/dist/client.js.map +0 -1
  17. package/dist/config.d.ts +0 -8
  18. package/dist/config.d.ts.map +0 -1
  19. package/dist/config.js +0 -8
  20. package/dist/config.js.map +0 -1
  21. package/dist/http-server.d.ts +0 -8
  22. package/dist/http-server.d.ts.map +0 -1
  23. package/dist/http-server.js +0 -481
  24. package/dist/http-server.js.map +0 -1
  25. package/dist/index.d.ts +0 -3
  26. package/dist/index.d.ts.map +0 -1
  27. package/dist/index.js.map +0 -1
  28. package/dist/resources/block-types.d.ts +0 -318
  29. package/dist/resources/block-types.d.ts.map +0 -1
  30. package/dist/resources/block-types.js +0 -297
  31. package/dist/resources/block-types.js.map +0 -1
  32. package/dist/resources/flow-schema.d.ts +0 -147
  33. package/dist/resources/flow-schema.d.ts.map +0 -1
  34. package/dist/resources/flow-schema.js +0 -143
  35. package/dist/resources/flow-schema.js.map +0 -1
  36. package/dist/server-factory.d.ts +0 -8
  37. package/dist/server-factory.d.ts.map +0 -1
  38. package/dist/server-factory.js +0 -82
  39. package/dist/server-factory.js.map +0 -1
  40. package/dist/tools/admin.d.ts +0 -265
  41. package/dist/tools/admin.d.ts.map +0 -1
  42. package/dist/tools/admin.js +0 -118
  43. package/dist/tools/admin.js.map +0 -1
  44. package/dist/tools/ai-designer.d.ts +0 -297
  45. package/dist/tools/ai-designer.d.ts.map +0 -1
  46. package/dist/tools/ai-designer.js +0 -89
  47. package/dist/tools/ai-designer.js.map +0 -1
  48. package/dist/tools/analytics.d.ts +0 -95
  49. package/dist/tools/analytics.d.ts.map +0 -1
  50. package/dist/tools/analytics.js +0 -44
  51. package/dist/tools/analytics.js.map +0 -1
  52. package/dist/tools/auth.d.ts +0 -61
  53. package/dist/tools/auth.d.ts.map +0 -1
  54. package/dist/tools/auth.js +0 -36
  55. package/dist/tools/auth.js.map +0 -1
  56. package/dist/tools/blocks.d.ts +0 -457
  57. package/dist/tools/blocks.d.ts.map +0 -1
  58. package/dist/tools/blocks.js +0 -173
  59. package/dist/tools/blocks.js.map +0 -1
  60. package/dist/tools/connections.d.ts +0 -173
  61. package/dist/tools/connections.d.ts.map +0 -1
  62. package/dist/tools/connections.js +0 -81
  63. package/dist/tools/connections.js.map +0 -1
  64. package/dist/tools/eduflow.d.ts +0 -409
  65. package/dist/tools/eduflow.d.ts.map +0 -1
  66. package/dist/tools/eduflow.js +0 -139
  67. package/dist/tools/eduflow.js.map +0 -1
  68. package/dist/tools/participants.d.ts +0 -221
  69. package/dist/tools/participants.d.ts.map +0 -1
  70. package/dist/tools/participants.js +0 -70
  71. package/dist/tools/participants.js.map +0 -1
  72. package/dist/tools/presentation.d.ts +0 -233
  73. package/dist/tools/presentation.d.ts.map +0 -1
  74. package/dist/tools/presentation.js +0 -57
  75. package/dist/tools/presentation.js.map +0 -1
  76. package/dist/tools/projects.d.ts +0 -131
  77. package/dist/tools/projects.d.ts.map +0 -1
  78. package/dist/tools/projects.js +0 -55
  79. package/dist/tools/projects.js.map +0 -1
  80. package/dist/tools/resources.d.ts +0 -93
  81. package/dist/tools/resources.d.ts.map +0 -1
  82. package/dist/tools/resources.js +0 -37
  83. package/dist/tools/resources.js.map +0 -1
  84. package/dist/tools/store.d.ts +0 -125
  85. package/dist/tools/store.d.ts.map +0 -1
  86. package/dist/tools/store.js +0 -66
  87. package/dist/tools/store.js.map +0 -1
  88. package/mcp-config.example.json +0 -14
  89. package/src/client.ts +0 -106
  90. package/src/config.ts +0 -7
  91. package/src/http-server.ts +0 -591
  92. package/src/index.ts +0 -23
  93. package/src/resources/block-types.ts +0 -298
  94. package/src/resources/flow-schema.ts +0 -148
  95. package/src/server-factory.ts +0 -109
  96. package/src/tools/admin.ts +0 -128
  97. package/src/tools/ai-designer.ts +0 -97
  98. package/src/tools/analytics.ts +0 -49
  99. package/src/tools/auth.ts +0 -39
  100. package/src/tools/blocks.ts +0 -186
  101. package/src/tools/connections.ts +0 -83
  102. package/src/tools/eduflow.ts +0 -150
  103. package/src/tools/participants.ts +0 -77
  104. package/src/tools/presentation.ts +0 -61
  105. package/src/tools/projects.ts +0 -61
  106. package/src/tools/resources.ts +0 -41
  107. package/src/tools/store.ts +0 -67
  108. package/tsconfig.json +0 -19
@@ -0,0 +1,80 @@
1
+ import { clearTokenCache, getLyrraRequestAuthHeaders, requestOrigin } from './auth-session.js';
2
+ function substitutePath(template, pathParams) {
3
+ let p = template;
4
+ const re = /\{([^}]+)\}/g;
5
+ let m;
6
+ const seen = new Set();
7
+ while ((m = re.exec(template)) !== null) {
8
+ seen.add(m[1]);
9
+ }
10
+ for (const name of seen) {
11
+ const val = pathParams[name];
12
+ if (val === undefined || val === '') {
13
+ throw new Error(`Required URL parameter: "${name}"`);
14
+ }
15
+ p = p.replaceAll(`{${name}}`, encodeURIComponent(val));
16
+ }
17
+ return p;
18
+ }
19
+ function buildQueryString(query) {
20
+ if (!query || Object.keys(query).length === 0)
21
+ return '';
22
+ const sp = new URLSearchParams();
23
+ for (const [k, v] of Object.entries(query)) {
24
+ if (v === undefined || v === null)
25
+ continue;
26
+ if (typeof v === 'object' && !Array.isArray(v)) {
27
+ sp.set(k, JSON.stringify(v));
28
+ }
29
+ else if (Array.isArray(v)) {
30
+ for (const item of v) {
31
+ sp.append(k, String(item));
32
+ }
33
+ }
34
+ else {
35
+ sp.set(k, String(v));
36
+ }
37
+ }
38
+ const s = sp.toString();
39
+ return s ? `?${s}` : '';
40
+ }
41
+ async function doFetch(method, url, authHeaders, body) {
42
+ const headers = {
43
+ Accept: 'application/json, text/plain, */*',
44
+ ...authHeaders,
45
+ };
46
+ let bodyInit;
47
+ if (body !== undefined && body !== null && method !== 'GET' && method !== 'HEAD') {
48
+ if (typeof body === 'string') {
49
+ bodyInit = body;
50
+ if (!headers['Content-Type'])
51
+ headers['Content-Type'] = 'text/plain; charset=utf-8';
52
+ }
53
+ else {
54
+ bodyInit = JSON.stringify(body);
55
+ headers['Content-Type'] = 'application/json; charset=utf-8';
56
+ }
57
+ }
58
+ const r = await fetch(url, { method, headers, body: bodyInit });
59
+ const bodyText = await r.text();
60
+ const outHeaders = {};
61
+ r.headers.forEach((v, k) => {
62
+ outHeaders[k] = v;
63
+ });
64
+ return { status: r.status, headers: outHeaders, bodyText };
65
+ }
66
+ export async function callLyrraOperation(method, openApiPath, input) {
67
+ const origin = requestOrigin();
68
+ const pathParams = input.pathParams ?? {};
69
+ const resolvedPath = substitutePath(openApiPath, pathParams);
70
+ const qs = buildQueryString(input.query);
71
+ const url = `${origin}${resolvedPath}${qs}`;
72
+ let authHeaders = await getLyrraRequestAuthHeaders();
73
+ let result = await doFetch(method.toUpperCase(), url, authHeaders, input.body);
74
+ if (result.status === 401) {
75
+ clearTokenCache();
76
+ authHeaders = await getLyrraRequestAuthHeaders();
77
+ result = await doFetch(method.toUpperCase(), url, authHeaders, input.body);
78
+ }
79
+ return result;
80
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Construction du McpServer Lyrra (outils OpenAPI + EduFlow) — partagé entre stdio et HTTP.
3
+ */
4
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
+ import { z } from 'zod';
6
+ import { requestOrigin } from './auth-session.js';
7
+ import { callLyrraOperation } from './lyrra-http.js';
8
+ import { fetchOpenApiJson, parseOpenApiOperations } from './openapi-parse.js';
9
+ import { eduflowBlockDocToolCount, registerEduflowBlockDocumentationTools, } from './register-eduflow-block-tools.js';
10
+ const MAX_DESC_CHARS = 12_000;
11
+ const MAX_TOOLS_ENV = process.env.LYRRA_MCP_MAX_TOOLS;
12
+ function truncateDesc(s) {
13
+ if (s.length <= MAX_DESC_CHARS)
14
+ return s;
15
+ return `${s.slice(0, MAX_DESC_CHARS)}\n\n[… truncated description — see OpenAPI / Swagger]`;
16
+ }
17
+ function zodShapeForOperation(op) {
18
+ const shape = {
19
+ query: z
20
+ .record(z.string(), z.unknown())
21
+ .optional()
22
+ .describe('Query parameters (key → value). Serialize complex values as JSON in a key if needed.'),
23
+ body: z
24
+ .unknown()
25
+ .optional()
26
+ .describe('JSON body (object or array) for POST, PUT, PATCH. Ignored for GET/DELETE.'),
27
+ };
28
+ for (const p of op.pathParamNames) {
29
+ shape[p] = z
30
+ .string()
31
+ .describe(`Value for path segment "${p}" in ${op.path}`);
32
+ }
33
+ return shape;
34
+ }
35
+ function formatToolResult(result) {
36
+ const snippet = result.bodyText.length > 120_000 ? `${result.bodyText.slice(0, 120_000)}\n… [truncated]` : result.bodyText;
37
+ const text = [
38
+ `HTTP ${result.status}`,
39
+ result.headers['content-type'] ? `Content-Type: ${result.headers['content-type']}` : '',
40
+ '',
41
+ snippet || '(empty body)',
42
+ ]
43
+ .filter(Boolean)
44
+ .join('\n');
45
+ return {
46
+ content: [{ type: 'text', text }],
47
+ isError: result.status >= 400,
48
+ };
49
+ }
50
+ export async function createLyrraMcpServer() {
51
+ process.stderr.write('[lyrra-mcp] Loading OpenAPI…\n');
52
+ const spec = await fetchOpenApiJson();
53
+ let ops = parseOpenApiOperations(spec);
54
+ const maxTools = MAX_TOOLS_ENV ? parseInt(MAX_TOOLS_ENV, 10) : NaN;
55
+ if (!Number.isNaN(maxTools) && maxTools > 0) {
56
+ ops = ops.slice(0, maxTools);
57
+ process.stderr.write(`[lyrra-mcp] LYRRA_MCP_MAX_TOOLS=${maxTools} — ${ops.length} tools registered.\n`);
58
+ }
59
+ process.stderr.write(`[lyrra-mcp] ${ops.length} operations → MCP tools.\n`);
60
+ const mcp = new McpServer({
61
+ name: 'lyrra-studio',
62
+ version: '1.0.0',
63
+ title: 'Lyrra Studio',
64
+ });
65
+ registerEduflowBlockDocumentationTools(mcp);
66
+ process.stderr.write(`[lyrra-mcp] EduFlow docs: ${eduflowBlockDocToolCount()} tools (index + per-block sheets).\n`);
67
+ mcp.registerTool('lyrra_meta', {
68
+ description: 'Configuration summary: API origin, registered tool count, useful environment variables (no secrets shown).',
69
+ inputSchema: z.object({}),
70
+ }, async () => {
71
+ let tokenMode = 'CLIENT_ID+SECRET';
72
+ if (process.env.LYRRA_ACCESS_TOKEN?.trim())
73
+ tokenMode = 'ACCESS_TOKEN';
74
+ const hn = process.env.LYRRA_MCP_HEADER_NAME?.trim() || process.env.LYRRA_HEADER_AUTH_NAME?.trim();
75
+ const hv = process.env.LYRRA_MCP_HEADER_SECRET?.trim() ||
76
+ process.env.LYRRA_MCP_HEADER_VALUE?.trim() ||
77
+ process.env.LYRRA_HEADER_AUTH_VALUE?.trim();
78
+ if (hn && hv)
79
+ tokenMode = 'HEADER_AUTH_ENV';
80
+ const text = [
81
+ `Request origin: ${requestOrigin()}`,
82
+ `OpenAPI tools registered: ${ops.length}`,
83
+ `EduFlow block doc tools: ${eduflowBlockDocToolCount()} (lyrra_eduflow_blocks_index + lyrra_eduflow_block_*)`,
84
+ `Token mode: ${tokenMode}`,
85
+ `HTTP MCP: forward Authorization, X-Lyrra-Api-Key, or X-Api-Key from each request (n8n Header Auth).`,
86
+ `LYRRA_API_URL: ${process.env.LYRRA_API_URL ? 'set' : 'missing'}`,
87
+ `LYRRA_OPENAPI_URL: ${process.env.LYRRA_OPENAPI_URL ? 'set' : 'default /api/openapi.json'}`,
88
+ ].join('\n');
89
+ return { content: [{ type: 'text', text }] };
90
+ });
91
+ mcp.registerTool('lyrra_search_operations', {
92
+ description: 'Search operations (operationId, method, path, start of description). Useful when the tool list is long.',
93
+ inputSchema: z.object({
94
+ q: z.string().min(1).describe('Search text (case-insensitive)'),
95
+ limit: z.number().int().min(1).max(80).optional().describe('Max results (default 30)'),
96
+ }),
97
+ }, async ({ q, limit }) => {
98
+ const lim = limit ?? 30;
99
+ const qq = q.toLowerCase();
100
+ const hits = ops
101
+ .filter((o) => o.operationId.toLowerCase().includes(qq) ||
102
+ o.path.toLowerCase().includes(qq) ||
103
+ o.method.toLowerCase().includes(qq) ||
104
+ o.description.toLowerCase().includes(qq))
105
+ .slice(0, lim);
106
+ const text = hits.length === 0
107
+ ? 'No operations found.'
108
+ : hits.map((o) => `${o.method} ${o.path}\n operationId: ${o.operationId}`).join('\n\n');
109
+ return { content: [{ type: 'text', text }] };
110
+ });
111
+ const staticGuide = `# Lyrra Studio — MCP integration
112
+
113
+ - Each tool named after an OpenAPI **operationId** calls **exactly** the documented endpoint.
114
+ - **EduFlow blocks**: tools \`lyrra_eduflow_blocks_index\` then \`lyrra_eduflow_block_<type>\` (e.g. \`lyrra_eduflow_block_quiz_mcq\`) — full sheet: role, \`data\`, \`settings\`, graph.
115
+ - API args: path segments \`{id}\` → required properties of the same name; \`query\`; \`body\` (JSON).
116
+ - Auth (stdio / env): \`LYRRA_CLIENT_ID\` + \`LYRRA_CLIENT_SECRET\`, \`LYRRA_ACCESS_TOKEN\`, or \`LYRRA_MCP_HEADER_NAME\` + value.
117
+ - Auth (HTTP MCP / n8n): send **Header Auth** on every MCP request (e.g. \`X-Lyrra-Api-Key: rak_…\` matching your institution key).
118
+ `;
119
+ mcp.registerResource('lyrra-mcp-guide', 'lyrra://flow-construction-guide', {
120
+ description: 'How to use the Lyrra MCP server',
121
+ mimeType: 'text/markdown',
122
+ }, async () => ({
123
+ contents: [{ uri: 'lyrra://flow-construction-guide', mimeType: 'text/markdown', text: staticGuide }],
124
+ }));
125
+ mcp.registerResource('lyrra-block-types', 'lyrra://block-types', {
126
+ description: 'Points to MCP tools lyrra_eduflow_block_* (per-type sheet) + persistence via eduflows API',
127
+ mimeType: 'text/markdown',
128
+ }, async () => ({
129
+ contents: [
130
+ {
131
+ uri: 'lyrra://block-types',
132
+ mimeType: 'text/markdown',
133
+ text: [
134
+ '# EduFlow block types (MCP)',
135
+ '',
136
+ `1. Call **lyrra_eduflow_blocks_index** for documented types (${eduflowBlockDocToolCount() - 1} blocks).`,
137
+ '2. Call **lyrra_eduflow_block_<type>** (e.g. `lyrra_eduflow_block_video`) for the full sheet: pedagogical role, `BlockData`, `settings`, connections.',
138
+ '3. HTTP persistence: OpenAPI routes under `/api/eduflows/{id}/blocks` and flow save.',
139
+ '',
140
+ 'Code reference: `apps/frontend/src/types/eduflow.ts`, palette `eduflow-designer-palette-blueprint.ts`.',
141
+ ].join('\n'),
142
+ },
143
+ ],
144
+ }));
145
+ for (const op of ops) {
146
+ const inputSchema = z.object(zodShapeForOperation(op));
147
+ const desc = truncateDesc(`${op.description}\n\n— HTTP ${op.method} \`${op.path}\``);
148
+ mcp.registerTool(op.operationId, {
149
+ description: desc,
150
+ inputSchema,
151
+ }, async (rawArgs) => {
152
+ const args = rawArgs;
153
+ const pathParams = {};
154
+ for (const name of op.pathParamNames) {
155
+ const v = args[name];
156
+ pathParams[name] = typeof v === 'string' ? v : String(v ?? '');
157
+ }
158
+ const query = args.query;
159
+ const body = args.body;
160
+ try {
161
+ const result = await callLyrraOperation(op.method, op.path, { pathParams, query, body });
162
+ return formatToolResult(result);
163
+ }
164
+ catch (e) {
165
+ const msg = e instanceof Error ? e.message : String(e);
166
+ return {
167
+ content: [{ type: 'text', text: `Error: ${msg}` }],
168
+ isError: true,
169
+ };
170
+ }
171
+ });
172
+ }
173
+ return { mcp, operationsCount: ops.length };
174
+ }
@@ -0,0 +1,61 @@
1
+ import { requestOrigin } from './auth-session.js';
2
+ const HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete']);
3
+ function pathParamsFromOpenApiPath(path) {
4
+ const names = [];
5
+ const re = /\{([^}]+)\}/g;
6
+ let m;
7
+ while ((m = re.exec(path)) !== null) {
8
+ names.push(m[1]);
9
+ }
10
+ return names;
11
+ }
12
+ export function parseOpenApiOperations(spec) {
13
+ if (!spec || typeof spec !== 'object')
14
+ return [];
15
+ const paths = spec.paths;
16
+ if (!paths || typeof paths !== 'object')
17
+ return [];
18
+ const out = [];
19
+ const usedIds = new Map();
20
+ for (const [path, item] of Object.entries(paths)) {
21
+ if (!item || typeof item !== 'object')
22
+ continue;
23
+ for (const [method, op] of Object.entries(item)) {
24
+ const m = method.toLowerCase();
25
+ if (!HTTP_METHODS.has(m))
26
+ continue;
27
+ if (!op || typeof op !== 'object')
28
+ continue;
29
+ const o = op;
30
+ let operationId = typeof o.operationId === 'string' && o.operationId.length > 0
31
+ ? o.operationId
32
+ : `${m}_${path.replace(/[^a-zA-Z0-9]+/g, '_')}`;
33
+ const n = (usedIds.get(operationId) ?? 0) + 1;
34
+ usedIds.set(operationId, n);
35
+ if (n > 1) {
36
+ operationId = `${operationId}__${n}`;
37
+ }
38
+ const summary = typeof o.summary === 'string' ? o.summary : `${m.toUpperCase()} ${path}`;
39
+ const description = typeof o.description === 'string' && o.description.length > 0 ? o.description : summary;
40
+ out.push({
41
+ operationId,
42
+ method: m.toUpperCase(),
43
+ path,
44
+ description,
45
+ pathParamNames: pathParamsFromOpenApiPath(path),
46
+ });
47
+ }
48
+ }
49
+ out.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
50
+ return out;
51
+ }
52
+ export async function fetchOpenApiJson() {
53
+ const override = process.env.LYRRA_OPENAPI_URL?.trim();
54
+ const origin = requestOrigin();
55
+ const url = override || `${origin}/api/openapi.json`;
56
+ const r = await fetch(url, { headers: { Accept: 'application/json' } });
57
+ if (!r.ok) {
58
+ throw new Error(`OpenAPI introuvable (${r.status}) : ${url}`);
59
+ }
60
+ return r.json();
61
+ }
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ import { EDUFLOW_BLOCK_DOCS, eduflowBlockToolName, renderEduflowBlockMarkdown, } from './eduflow-block-docs.js';
3
+ /** MCP tools: one per block type + index (detailed JSON/settings sheets). */
4
+ export function registerEduflowBlockDocumentationTools(mcp) {
5
+ mcp.registerTool('lyrra_eduflow_blocks_index', {
6
+ description: 'Lists MCP tools `lyrra_eduflow_block_*` (one per EduFlow block type) with a short summary; call them for full docs (data, settings, graph).',
7
+ inputSchema: z.object({}),
8
+ }, async () => {
9
+ const lines = EDUFLOW_BLOCK_DOCS.map((e) => `- \`${eduflowBlockToolName(e.id)}\` — **${e.label}** (\`${e.id}\`, ${e.category}): ${e.summary}`);
10
+ const text = [
11
+ '# Index — EduFlow block types',
12
+ '',
13
+ 'Each line is a distinct **MCP tool**. Invoke it with no arguments for full documentation (JSON model, configuration).',
14
+ '',
15
+ ...lines,
16
+ ].join('\n');
17
+ return { content: [{ type: 'text', text }] };
18
+ });
19
+ for (const e of EDUFLOW_BLOCK_DOCS) {
20
+ const toolName = eduflowBlockToolName(e.id);
21
+ mcp.registerTool(toolName, {
22
+ description: `EduFlow block “${e.label}” (type \`${e.id}\`) — ${e.summary}`,
23
+ inputSchema: z.object({}),
24
+ }, async () => ({
25
+ content: [{ type: 'text', text: renderEduflowBlockMarkdown(e.id) }],
26
+ }));
27
+ }
28
+ }
29
+ export function eduflowBlockDocToolCount() {
30
+ return EDUFLOW_BLOCK_DOCS.length + 1;
31
+ }
package/package.json CHANGED
@@ -1,28 +1,56 @@
1
1
  {
2
2
  "name": "@lyrra/mcp-server",
3
- "version": "1.1.3",
4
- "description": "LYRRA Studio MCP Server - Pilotez LYRRA Studio depuis Claude AI, Cursor, etc.",
3
+ "version": "1.1.7",
4
+ "description": "Lyrra Studio stdio MCP server — OpenAPI-aligned tools for Claude Desktop, Cursor, and compatible MCP clients",
5
5
  "type": "module",
6
- "main": "dist/index.js",
6
+ "main": "./dist/index.js",
7
7
  "bin": {
8
- "lyrra-mcp": "dist/index.js"
8
+ "lyrra-mcp": "./dist/index.js",
9
+ "lyrra-mcp-http": "./dist/http-main.js"
9
10
  },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
10
15
  "scripts": {
11
- "build": "tsc",
16
+ "build": "tsc -p tsconfig.json",
17
+ "prepublishOnly": "npm run build",
12
18
  "start": "node dist/index.js",
13
- "start:http": "node dist/http-server.js",
19
+ "start:http": "node dist/http-main.js",
14
20
  "dev": "tsx src/index.ts",
15
- "dev:http": "tsx src/http-server.ts"
21
+ "dev:http": "tsx src/http-main.ts",
22
+ "test:mcp": "npm run build && node scripts/mcp-smoke-test.mjs"
23
+ },
24
+ "engines": {
25
+ "node": ">=20.0.0"
26
+ },
27
+ "keywords": [
28
+ "mcp",
29
+ "model-context-protocol",
30
+ "lyrra",
31
+ "lyrra-studio",
32
+ "openapi",
33
+ "claude",
34
+ "stdio"
35
+ ],
36
+ "author": "Lyrra",
37
+ "license": "UNLICENSED",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/LyrraStudio/LyrraStudio.git",
41
+ "directory": "lyrra-studio-app/apps/mcp-server"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
16
45
  },
17
46
  "dependencies": {
18
- "@modelcontextprotocol/sdk": "^1.12.1",
19
- "@types/cors": "^2.8.19",
20
- "@types/express": "^5.0.6",
21
- "cors": "^2.8.6",
22
- "zod": "^3.24.0"
47
+ "@modelcontextprotocol/sdk": "^1.29.0",
48
+ "express": "^5.2.1",
49
+ "zod": "^3.23.8"
23
50
  },
24
51
  "devDependencies": {
25
- "@types/node": "^20.19.26",
52
+ "@types/express": "^5.0.0",
53
+ "@types/node": "^20.10.8",
26
54
  "tsx": "^4.7.0",
27
55
  "typescript": "^5.2.2"
28
56
  }
package/Dockerfile DELETED
@@ -1,16 +0,0 @@
1
- FROM node:20-slim
2
-
3
- WORKDIR /app
4
-
5
- COPY package*.json ./
6
- COPY tsconfig.json ./
7
-
8
- RUN npm install --legacy-peer-deps
9
-
10
- COPY src ./src
11
-
12
- RUN npm run build
13
-
14
- EXPOSE 3002
15
-
16
- CMD ["node", "dist/http-server.js"]
package/dist/client.d.ts DELETED
@@ -1,23 +0,0 @@
1
- export interface LyrraClientOptions {
2
- apiUrl?: string;
3
- eduflowUrl?: string;
4
- clientId?: string;
5
- clientSecret?: string;
6
- }
7
- export declare class LyrraClient {
8
- private apiUrl;
9
- private eduflowUrl;
10
- private clientId;
11
- private clientSecret;
12
- private jwtToken;
13
- constructor(options?: LyrraClientOptions);
14
- private get headers();
15
- setToken(token: string): void;
16
- get(path: string, base?: 'api' | 'eduflow'): Promise<any>;
17
- post(path: string, data?: any, base?: 'api' | 'eduflow'): Promise<any>;
18
- put(path: string, data?: any, base?: 'api' | 'eduflow'): Promise<any>;
19
- patch(path: string, data?: any, base?: 'api' | 'eduflow'): Promise<any>;
20
- delete(path: string, base?: 'api' | 'eduflow'): Promise<any>;
21
- }
22
- export declare const client: LyrraClient;
23
- //# sourceMappingURL=client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAuB;gBAE3B,OAAO,CAAC,EAAE,kBAAkB;IAOxC,OAAO,KAAK,OAAO,GAMlB;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM;IAIhB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,KAAK,GAAG,SAAiB,GAAG,OAAO,CAAC,GAAG,CAAC;IAUhE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,GAAE,KAAK,GAAG,SAAiB,GAAG,OAAO,CAAC,GAAG,CAAC;IAe7E,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,GAAE,KAAK,GAAG,SAAiB,GAAG,OAAO,CAAC,GAAG,CAAC;IAe5E,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,GAAE,KAAK,GAAG,SAAiB,GAAG,OAAO,CAAC,GAAG,CAAC;IAe9E,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,KAAK,GAAG,SAAiB,GAAG,OAAO,CAAC,GAAG,CAAC;CAa1E;AAED,eAAO,MAAM,MAAM,aAAoB,CAAC"}
package/dist/client.js DELETED
@@ -1,92 +0,0 @@
1
- import { config } from './config.js';
2
- export class LyrraClient {
3
- apiUrl;
4
- eduflowUrl;
5
- clientId;
6
- clientSecret;
7
- jwtToken = null;
8
- constructor(options) {
9
- this.apiUrl = options?.apiUrl || config.apiUrl;
10
- this.eduflowUrl = options?.eduflowUrl || config.eduflowApiUrl;
11
- this.clientId = options?.clientId || config.clientId;
12
- this.clientSecret = options?.clientSecret || config.clientSecret;
13
- }
14
- get headers() {
15
- const h = { 'Content-Type': 'application/json' };
16
- // Client Secret is the API key used for X-API-Key header authentication
17
- if (this.clientSecret)
18
- h['X-API-Key'] = this.clientSecret;
19
- if (this.jwtToken)
20
- h['Authorization'] = `Bearer ${this.jwtToken}`;
21
- return h;
22
- }
23
- setToken(token) {
24
- this.jwtToken = token;
25
- }
26
- async get(path, base = 'api') {
27
- const url = `${base === 'eduflow' ? this.eduflowUrl : this.apiUrl}${path}`;
28
- const res = await fetch(url, { headers: this.headers });
29
- if (!res.ok) {
30
- const body = await res.text();
31
- throw new Error(`GET ${path} failed (${res.status}): ${body}`);
32
- }
33
- return res.json();
34
- }
35
- async post(path, data, base = 'api') {
36
- const url = `${base === 'eduflow' ? this.eduflowUrl : this.apiUrl}${path}`;
37
- const res = await fetch(url, {
38
- method: 'POST',
39
- headers: this.headers,
40
- body: data ? JSON.stringify(data) : undefined,
41
- });
42
- if (!res.ok) {
43
- const body = await res.text();
44
- throw new Error(`POST ${path} failed (${res.status}): ${body}`);
45
- }
46
- const text = await res.text();
47
- return text ? JSON.parse(text) : {};
48
- }
49
- async put(path, data, base = 'api') {
50
- const url = `${base === 'eduflow' ? this.eduflowUrl : this.apiUrl}${path}`;
51
- const res = await fetch(url, {
52
- method: 'PUT',
53
- headers: this.headers,
54
- body: data ? JSON.stringify(data) : undefined,
55
- });
56
- if (!res.ok) {
57
- const body = await res.text();
58
- throw new Error(`PUT ${path} failed (${res.status}): ${body}`);
59
- }
60
- const text = await res.text();
61
- return text ? JSON.parse(text) : {};
62
- }
63
- async patch(path, data, base = 'api') {
64
- const url = `${base === 'eduflow' ? this.eduflowUrl : this.apiUrl}${path}`;
65
- const res = await fetch(url, {
66
- method: 'PATCH',
67
- headers: this.headers,
68
- body: data ? JSON.stringify(data) : undefined,
69
- });
70
- if (!res.ok) {
71
- const body = await res.text();
72
- throw new Error(`PATCH ${path} failed (${res.status}): ${body}`);
73
- }
74
- const text = await res.text();
75
- return text ? JSON.parse(text) : {};
76
- }
77
- async delete(path, base = 'api') {
78
- const url = `${base === 'eduflow' ? this.eduflowUrl : this.apiUrl}${path}`;
79
- const res = await fetch(url, {
80
- method: 'DELETE',
81
- headers: this.headers,
82
- });
83
- if (!res.ok) {
84
- const body = await res.text();
85
- throw new Error(`DELETE ${path} failed (${res.status}): ${body}`);
86
- }
87
- const text = await res.text();
88
- return text ? JSON.parse(text) : {};
89
- }
90
- }
91
- export const client = new LyrraClient();
92
- //# sourceMappingURL=client.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts DELETED
@@ -1,8 +0,0 @@
1
- export declare const config: {
2
- apiUrl: string;
3
- clientId: string;
4
- clientSecret: string;
5
- eduflowApiUrl: string;
6
- frontendUrl: string;
7
- };
8
- //# sourceMappingURL=config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;;;CAMlB,CAAC"}
package/dist/config.js DELETED
@@ -1,8 +0,0 @@
1
- export const config = {
2
- apiUrl: process.env.LYRRA_API_URL || 'http://localhost:3001/api',
3
- clientId: process.env.LYRRA_CLIENT_ID || '',
4
- clientSecret: process.env.LYRRA_CLIENT_SECRET || '',
5
- eduflowApiUrl: process.env.LYRRA_EDUFLOW_API_URL || 'http://localhost:3001/eduflow',
6
- frontendUrl: process.env.LYRRA_FRONTEND_URL || (process.env.LYRRA_API_URL ? process.env.LYRRA_API_URL.replace(/\/api$/, '') : 'http://localhost:5173'),
7
- };
8
- //# sourceMappingURL=config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,2BAA2B;IAChE,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE;IAC3C,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE;IACnD,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,+BAA+B;IACnF,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC;CACvJ,CAAC"}
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * LYRRA Studio MCP HTTP Server
4
- * Provides Streamable HTTP transport with OAuth 2.0 for Claude.ai integration.
5
- * All endpoints are under /mcp/ for clean nginx routing.
6
- */
7
- export {};
8
- //# sourceMappingURL=http-server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":";AACA;;;;GAIG"}