icode-mcp-adapter 1.0.10 → 1.0.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icode-mcp-adapter",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Dynamic MCP server adapter — auto-generates CRUD tools from schema configs. Plugs into icode-server via Fastify or runs standalone.",
5
5
  "type": "module",
6
6
  "main": "src/engine/McpEngine.js",
@@ -12,9 +12,9 @@ import { resolveSchemas as defaultResolveSchemas } from './SchemaAdapter.js';
12
12
  */
13
13
  export function createMcpServer(config, db, identity, newGuid, writer) {
14
14
  const server = new McpServer({
15
- name: `${config.appName}-mcp`,
15
+ name: config.serverName || `${config.appName}-mcp`,
16
16
  version: config.version,
17
- });
17
+ }, config.instructions ? { instructions: config.instructions } : undefined);
18
18
 
19
19
  for (const table of config.tables) {
20
20
  registerTableTools(server, db, table, identity, newGuid, writer);
@@ -39,13 +39,15 @@ export async function createMcpServerAuto(config, db, opts = {}) {
39
39
  const resolveSchemas = opts.resolveSchemas || defaultResolveSchemas;
40
40
 
41
41
  const tables = await resolveSchemas(db, config.tables, {
42
- cacheKey: `${config.appName}--${config.env || 'prod'}`,
42
+ cacheKey: config.cacheKey || `${config.appName}--${config.env || 'prod'}`,
43
43
  ownerColumn: config.ownerColumn,
44
44
  });
45
45
 
46
46
  return createMcpServer({
47
47
  appName: config.appName,
48
+ serverName: config.serverName,
48
49
  version: config.version || '1.0.0',
50
+ instructions: config.instructions,
49
51
  tables,
50
52
  }, db, opts.identity, opts.newGuid, opts.writer);
51
53
  }
@@ -40,9 +40,20 @@
40
40
  * @property {string} name - App identifier (e.g., 'paaal')
41
41
  * @property {string} displayName - Human-readable (e.g., 'PAAAL Coach')
42
42
  * @property {string} version
43
+ * @property {string} [instructions] - Agent-facing usage guidance, sent to clients in the MCP initialize response
43
44
  * @property {DbConfig} db
44
45
  * @property {string} [ownerColumn] - App-wide default owner column for row-level scoping (per-table `ownerColumn` overrides)
45
46
  * @property {TableSchema[]} tables
47
+ * @property {Record<string, SubServer>} [servers] - Optional grouped views over the app.
48
+ * The root URL stays the INDEX server (all app-level tables + instructions);
49
+ * each key is additionally served at /:appName/<key> as its own MCP server,
50
+ * inheriting from the app config and overriding what it declares.
51
+ *
52
+ * @typedef {Object} SubServer
53
+ * @property {string} [instructions] - Overrides the app-level instructions for this sub-server
54
+ * @property {Object} [tables] - Overrides the app-level tables (same shape; a table may
55
+ * appear in several sub-servers with different operations). Omit to inherit all.
56
+ * @property {string|null} [ownerColumn] - Overrides the app-level owner column
46
57
  */
47
58
 
48
59
  export {};
@@ -13,13 +13,18 @@
13
13
  * });
14
14
  *
15
15
  * Route param format:
16
- * /v1/mcp/paaal--dev → appName: 'paaal', env: 'dev'
17
- * /v1/mcp/paaal → appName: 'paaal', env: 'prod' (default)
16
+ * /v1/mcp/paaal--dev → appName: 'paaal', env: 'dev'
17
+ * /v1/mcp/paaal → appName: 'paaal', env: 'prod' (default)
18
+ * /v1/mcp/paaal--dev/manage → sub-server 'manage' (when the config defines servers.manage)
18
19
  *
19
- * Routes:
20
- * POST /v1/mcp/:appName MCP protocol endpoint
21
- * DELETE /v1/mcp/:appName — Session termination
22
- * GET /v1/mcp/:appName/health — Health check
20
+ * The root URL is always the app's INDEX server (all app-level tables + instructions).
21
+ * Sub-servers are optional grouped views over it each inherits from the app config
22
+ * and overrides what it declares (tables, instructions, ownerColumn).
23
+ *
24
+ * Routes (the /:sub variants serve apps whose config defines `servers`):
25
+ * POST /v1/mcp/:appName[/:sub] — MCP protocol endpoint
26
+ * DELETE /v1/mcp/:appName[/:sub] — Session termination
27
+ * GET /v1/mcp/:appName[/:sub]/health — Health check
23
28
  */
24
29
 
25
30
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
@@ -104,22 +109,37 @@ export default async function mcpPlugin(fastify, opts = {}) {
104
109
  }
105
110
  }
106
111
 
107
- // Build lookup: key = "appName--env" (or "appName--prod" for default)
112
+ // Build lookup: key = "appName--env" (or "appName--prod" for default).
113
+ // The root key is the app's INDEX server — all app-level tables + instructions.
114
+ // Each `servers` entry is additionally served at "appName--env/sub" as its own
115
+ // MCP server, inheriting from the app config and overriding what it declares
116
+ // (tables, instructions, ownerColumn) — grouped tools per agent.
108
117
  const apps = new Map();
109
118
  for (const config of Object.values(opts.apps || {})) {
110
- const key = `${config.appName}--${config.env || 'prod'}`;
111
- apps.set(key, config);
119
+ const base = `${config.appName}--${config.env || 'prod'}`;
120
+ apps.set(base, config);
121
+ for (const [sub, server] of Object.entries(config.servers || {})) {
122
+ apps.set(`${base}/${sub}`, {
123
+ appName: config.appName,
124
+ env: config.env,
125
+ version: config.version,
126
+ ownerColumn: server.ownerColumn !== undefined ? server.ownerColumn : config.ownerColumn,
127
+ instructions: server.instructions || config.instructions,
128
+ tables: server.tables || config.tables,
129
+ serverName: `${config.appName}-${sub}-mcp`,
130
+ cacheKey: `${base}--${sub}`,
131
+ });
132
+ }
112
133
  }
113
134
  if (!apps.size) throw new Error('icode-mcp-adapter: opts.apps is required (at least one app)');
114
135
 
115
136
  // ── POST — MCP protocol endpoint ───────────────────────────────────────
116
- fastify.post(`${prefix}/:appName`, {
117
- config: { public: isPublic },
118
- }, async (req, reply) => {
137
+ async function handleMcpPost(req, reply) {
119
138
  const { appName, env } = parseAppParam(req.params.appName);
120
- const appConfig = apps.get(`${appName}--${env}`);
139
+ const key = req.params.sub ? `${appName}--${env}/${req.params.sub}` : `${appName}--${env}`;
140
+ const appConfig = apps.get(key);
121
141
  if (!appConfig) {
122
- return reply.code(404).send({ ok: false, error: `App '${appName}/${env}' not registered` });
142
+ return reply.code(404).send({ ok: false, error: `App '${key}' not registered` });
123
143
  }
124
144
 
125
145
  const sessionId = req.headers['mcp-session-id'];
@@ -176,19 +196,23 @@ export default async function mcpPlugin(fastify, opts = {}) {
176
196
  error: { code: -32000, message: 'Invalid session or missing initialization' },
177
197
  id: null,
178
198
  });
179
- });
199
+ }
200
+
201
+ fastify.post(`${prefix}/:appName`, { config: { public: isPublic } }, handleMcpPost);
202
+ fastify.post(`${prefix}/:appName/:sub`, { config: { public: isPublic } }, handleMcpPost);
180
203
 
181
204
  // ── DELETE — Session termination ───────────────────────────────────────
182
- fastify.delete(`${prefix}/:appName`, {
183
- config: { public: isPublic },
184
- }, async (req, reply) => {
205
+ async function handleMcpDelete(req, reply) {
185
206
  const sessionId = req.headers['mcp-session-id'];
186
207
  if (sessionId && sessions.has(sessionId)) {
187
208
  await sessions.get(sessionId).transport.close();
188
209
  sessions.delete(sessionId);
189
210
  }
190
211
  return reply.code(200).send();
191
- });
212
+ }
213
+
214
+ fastify.delete(`${prefix}/:appName`, { config: { public: isPublic } }, handleMcpDelete);
215
+ fastify.delete(`${prefix}/:appName/:sub`, { config: { public: isPublic } }, handleMcpDelete);
192
216
 
193
217
  // ── GET — Health check ─────────────────────────────────────────────────
194
218
  fastify.get(`${prefix}/:appName/health`, {
@@ -197,6 +221,18 @@ export default async function mcpPlugin(fastify, opts = {}) {
197
221
  const { appName, env } = parseAppParam(req.params.appName);
198
222
  const appConfig = apps.get(`${appName}--${env}`);
199
223
  if (!appConfig) return { ok: false, error: `App '${appName}/${env}' not registered` };
200
- return { ok: true, app: appName, env, tables: Object.keys(appConfig.tables) };
224
+ const out = { ok: true, app: appName, env, tables: Object.keys(appConfig.tables) };
225
+ if (appConfig.servers) out.servers = Object.keys(appConfig.servers);
226
+ return out;
227
+ });
228
+
229
+ fastify.get(`${prefix}/:appName/:sub/health`, {
230
+ config: { public: isPublic },
231
+ }, async (req) => {
232
+ const { appName, env } = parseAppParam(req.params.appName);
233
+ const key = `${appName}--${env}/${req.params.sub}`;
234
+ const appConfig = apps.get(key);
235
+ if (!appConfig) return { ok: false, error: `App '${key}' not registered` };
236
+ return { ok: true, app: appName, env, server: req.params.sub, tables: Object.keys(appConfig.tables) };
201
237
  });
202
238
  }