@gavdi/cap-mcp 0.9.4 → 0.9.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.
package/lib/mcp/tools.js CHANGED
@@ -43,7 +43,10 @@ function assignBoundOperation(params, model, server, authEnabled) {
43
43
  description: model.description,
44
44
  inputSchema: inputSchema,
45
45
  }, async (args) => {
46
- const service = cds.services[model.serviceName];
46
+ // Resolve from current CAP context; prefer global to align with Jest mocks
47
+ const cdsMod = global.cds || cds;
48
+ const servicesMap = cdsMod.services || (cdsMod.services = {});
49
+ const service = servicesMap[model.serviceName];
47
50
  if (!service) {
48
51
  logger_1.LOGGER.error("Invalid CAP service - undefined");
49
52
  return {
@@ -73,14 +76,7 @@ function assignBoundOperation(params, model, server, authEnabled) {
73
76
  data: operationInput,
74
77
  params: [operationKeys],
75
78
  });
76
- return {
77
- content: Array.isArray(response)
78
- ? response.map((el) => ({
79
- type: "text",
80
- text: formatResponseValue(el),
81
- }))
82
- : [{ type: "text", text: formatResponseValue(response) }],
83
- };
79
+ return (0, utils_1.asMcpResult)(response);
84
80
  });
85
81
  }
86
82
  /**
@@ -97,7 +93,10 @@ function assignUnboundOperation(params, model, server, authEnabled) {
97
93
  description: model.description,
98
94
  inputSchema: inputSchema,
99
95
  }, async (args) => {
100
- const service = cds.services[model.serviceName];
96
+ // Resolve from current CAP context; prefer global to align with Jest mocks
97
+ const cdsMod = global.cds || cds;
98
+ const servicesMap = cdsMod.services || (cdsMod.services = {});
99
+ const service = servicesMap[model.serviceName];
101
100
  if (!service) {
102
101
  logger_1.LOGGER.error("Invalid CAP service - undefined");
103
102
  return {
@@ -114,14 +113,7 @@ function assignUnboundOperation(params, model, server, authEnabled) {
114
113
  const response = await service
115
114
  .tx({ user: accessRights })
116
115
  .send(model.target, args);
117
- return {
118
- content: Array.isArray(response)
119
- ? response.map((el) => ({
120
- type: "text",
121
- text: formatResponseValue(el),
122
- }))
123
- : [{ type: "text", text: formatResponseValue(response) }],
124
- };
116
+ return (0, utils_1.asMcpResult)(response);
125
117
  });
126
118
  }
127
119
  /**
package/lib/mcp/utils.js CHANGED
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.determineMcpParameterType = determineMcpParameterType;
4
4
  exports.handleMcpSessionRequest = handleMcpSessionRequest;
5
5
  exports.writeODataDescriptionForResource = writeODataDescriptionForResource;
6
+ exports.toolError = toolError;
7
+ exports.asMcpResult = asMcpResult;
6
8
  const constants_1 = require("./constants");
7
9
  const zod_1 = require("zod");
8
10
  /**
@@ -69,3 +71,57 @@ function writeODataDescriptionForResource(model) {
69
71
  }
70
72
  return description;
71
73
  }
74
+ /**
75
+ * Unified MCP tool error response helper
76
+ * Returns a consistent JSON error payload inside MCP content
77
+ */
78
+ function toolError(code, message, extra) {
79
+ const payload = { error: code, message, ...(extra || {}) };
80
+ return {
81
+ isError: true,
82
+ content: [
83
+ {
84
+ type: "text",
85
+ text: JSON.stringify(payload),
86
+ },
87
+ ],
88
+ };
89
+ }
90
+ /**
91
+ * Formats a payload as MCP result content with a single text part.
92
+ * This ensures compatibility with all MCP clients.
93
+ */
94
+ function asMcpResult(payload) {
95
+ // Pretty-print for objects, stringify primitives, and split arrays into multiple parts
96
+ const toText = (value) => {
97
+ if (typeof value === "string")
98
+ return value;
99
+ if (value === undefined)
100
+ return "undefined";
101
+ try {
102
+ if (value !== null && typeof value === "object") {
103
+ return JSON.stringify(value, null, 2);
104
+ }
105
+ return String(value);
106
+ }
107
+ catch {
108
+ // Circular structures fall back to default string conversion
109
+ return String(value);
110
+ }
111
+ };
112
+ if (Array.isArray(payload)) {
113
+ if (payload.length === 0)
114
+ return { content: [] };
115
+ return {
116
+ content: payload.map((item) => ({ type: "text", text: toText(item) })),
117
+ };
118
+ }
119
+ return {
120
+ content: [
121
+ {
122
+ type: "text",
123
+ text: toText(payload),
124
+ },
125
+ ],
126
+ };
127
+ }
package/lib/mcp.js CHANGED
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const cds_1 = __importDefault(require("@sap/cds"));
7
6
  const logger_1 = require("./logger");
8
7
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
9
8
  const express_1 = __importDefault(require("express"));
@@ -14,8 +13,7 @@ const loader_1 = require("./config/loader");
14
13
  const session_manager_1 = require("./mcp/session-manager");
15
14
  const utils_2 = require("./auth/utils");
16
15
  /* @ts-ignore */
17
- // const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
18
- // TODO: Handle auth
16
+ const cds = global.cds; // Use hosting app's CDS instance exclusively
19
17
  /**
20
18
  * Main MCP plugin class that integrates CAP services with Model Context Protocol
21
19
  * Manages server sessions, API endpoints, and annotation processing
@@ -85,7 +83,26 @@ class McpPlugin {
85
83
  });
86
84
  this.registerMcpSessionRoute();
87
85
  this.expressApp?.get("/mcp", (req, res) => (0, utils_1.handleMcpSessionRequest)(req, res, this.sessionManager.getSessions()));
88
- this.expressApp?.delete("/mcp", (req, res) => (0, utils_1.handleMcpSessionRequest)(req, res, this.sessionManager.getSessions()));
86
+ this.expressApp?.delete("/mcp", ((req, res) => {
87
+ const sessionIdHeader = req.headers[constants_1.MCP_SESSION_HEADER];
88
+ const sessions = this.sessionManager.getSessions();
89
+ if (!sessionIdHeader || !sessions.has(sessionIdHeader)) {
90
+ return res.status(400).json({
91
+ jsonrpc: "2.0",
92
+ error: {
93
+ code: -32000,
94
+ message: "Bad Request: No valid sessions ID provided",
95
+ id: null,
96
+ },
97
+ });
98
+ }
99
+ const session = sessions.get(sessionIdHeader);
100
+ // Fire-and-forget close operations
101
+ void session.transport.close();
102
+ void session.server.close();
103
+ sessions.delete(sessionIdHeader);
104
+ return res.status(200).json({ jsonrpc: "2.0", result: { closed: true } });
105
+ }));
89
106
  }
90
107
  /**
91
108
  * Registers the main MCP POST endpoint for session creation and request handling
@@ -94,7 +111,6 @@ class McpPlugin {
94
111
  registerMcpSessionRoute() {
95
112
  logger_1.LOGGER.debug("Registering MCP entry point");
96
113
  this.expressApp?.post("/mcp", async (req, res) => {
97
- logger_1.LOGGER.debug("CONTEXT", cds_1.default.context); // TODO: Remove this line after testing
98
114
  const sessionIdHeader = req.headers[constants_1.MCP_SESSION_HEADER];
99
115
  logger_1.LOGGER.debug("MCP request received", {
100
116
  hasSessionId: !!sessionIdHeader,
@@ -117,7 +133,9 @@ class McpPlugin {
117
133
  return;
118
134
  }
119
135
  try {
136
+ const t0 = Date.now();
120
137
  await session.transport.handleRequest(req, res, req.body);
138
+ logger_1.LOGGER.debug("MCP request handled", { durationMs: Date.now() - t0 });
121
139
  return;
122
140
  }
123
141
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gavdi/cap-mcp",
3
- "version": "0.9.4",
3
+ "version": "0.9.7",
4
4
  "description": "MCP Pluging for CAP",
5
5
  "keywords": [
6
6
  "MCP",
@@ -22,6 +22,7 @@
22
22
  "repository": "github:gavdilabs/cap-mcp-plugin",
23
23
  "scripts": {
24
24
  "mock": "npm run start --workspace=test/demo",
25
+ "mock:watch": "npm run build && npm run watch --workspace=test/demo",
25
26
  "test": "NODE_ENV=test jest --silent",
26
27
  "test:unit": "NODE_ENV=test jest --silent test/unit",
27
28
  "test:integration": "NODE_ENV=test jest --silent test/integration",
@@ -30,7 +31,10 @@
30
31
  "prepare": "husky",
31
32
  "lint": "eslint",
32
33
  "lint-staged": "lint-staged",
33
- "format": "prettier --check ./src"
34
+ "format": "prettier --check ./src",
35
+ "format:fix": "prettier --write ./src \"**/*.json\"",
36
+ "build:watch": "tsc --watch",
37
+ "release": "release-it"
34
38
  },
35
39
  "peerDependencies": {
36
40
  "@sap/cds": "^9",
@@ -43,6 +47,7 @@
43
47
  },
44
48
  "devDependencies": {
45
49
  "@cap-js/cds-types": "^0.12.0",
50
+ "@release-it/conventional-changelog": "^10.0.1",
46
51
  "@types/express": "^5.0.3",
47
52
  "@types/jest": "^30.0.0",
48
53
  "@types/node": "^24.0.3",
@@ -53,7 +58,7 @@
53
58
  "jest": "^29.7.0",
54
59
  "lint-staged": "^16.1.2",
55
60
  "prettier": "^3.5.3",
56
- "release-it": "^19.0.3",
61
+ "release-it": "^19.0.4",
57
62
  "sinon": "^21.0.0",
58
63
  "supertest": "^7.0.0",
59
64
  "ts-jest": "^29.4.0",
@@ -62,15 +67,15 @@
62
67
  },
63
68
  "lint-staged": {
64
69
  "*.json": [
65
- "prettier --check"
70
+ "prettier --write"
66
71
  ],
67
72
  "*.ts": [
68
73
  "eslint",
69
- "prettier ./src --check"
74
+ "prettier --write"
70
75
  ],
71
76
  "*.js": [
72
77
  "eslint",
73
- "prettier ./src --check"
78
+ "prettier --write"
74
79
  ]
75
80
  },
76
81
  "workspaces": [
@@ -1,257 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.McpPromptAnnotation = exports.McpToolAnnotation = exports.McpResourceAnnotation = exports.McpAnnotation = exports.McpAnnotations = exports.McpAnnotationKey = void 0;
4
- exports.parseAnnotations = parseAnnotations;
5
- const utils_1 = require("./utils");
6
- const DEFAULT_ALL_RESOURCE_OPTIONS = new Set([
7
- "filter",
8
- "sort",
9
- "top",
10
- "skip",
11
- "select",
12
- ]);
13
- exports.McpAnnotationKey = "@mcp";
14
- exports.McpAnnotations = {
15
- // Resource annotations for MCP
16
- MCP_RESOURCE: "@mcp.resource",
17
- // Tool annotations for MCP
18
- MCP_TOOL_NAME: "@mcp.tool.name",
19
- MCP_TOOL_DESCRIPTION: "@mcp.tool.description",
20
- // Prompt annotations for MCP
21
- MCP_PROMPT: "@mcp.prompt",
22
- };
23
- class McpAnnotation {
24
- _target;
25
- _serviceName;
26
- constructor(target, serviceName) {
27
- this._target = target;
28
- this._serviceName = serviceName;
29
- }
30
- get target() {
31
- return this._target;
32
- }
33
- get serviceName() {
34
- return this._serviceName;
35
- }
36
- }
37
- exports.McpAnnotation = McpAnnotation;
38
- class McpResourceAnnotation extends McpAnnotation {
39
- _includeAll;
40
- _functionalities;
41
- _properties;
42
- constructor(target, serviceName, includeAll, functionalities, properties) {
43
- super(target, serviceName);
44
- this._includeAll = includeAll;
45
- this._functionalities = functionalities;
46
- this._properties = properties;
47
- }
48
- get includeAll() {
49
- return this._includeAll;
50
- }
51
- get functionalities() {
52
- return this._functionalities;
53
- }
54
- get properties() {
55
- return this._properties;
56
- }
57
- }
58
- exports.McpResourceAnnotation = McpResourceAnnotation;
59
- class McpToolAnnotation extends McpAnnotation {
60
- _name;
61
- _description;
62
- _parameters;
63
- _entityKey;
64
- _operationKind;
65
- _keyTypeMap;
66
- constructor(name, description, operation, serviceName, parameters, entityKey, operationKind, keyTypeMap) {
67
- super(operation, serviceName);
68
- this._name = name;
69
- this._description = description;
70
- this._parameters = parameters;
71
- this._entityKey = entityKey;
72
- this._operationKind = operationKind;
73
- this._keyTypeMap = keyTypeMap;
74
- }
75
- get name() {
76
- return this._name;
77
- }
78
- get description() {
79
- return this._description;
80
- }
81
- get parameters() {
82
- return this._parameters;
83
- }
84
- get entityKey() {
85
- return this._entityKey;
86
- }
87
- get operationKind() {
88
- return this._operationKind;
89
- }
90
- get keyTypeMap() {
91
- return this._keyTypeMap;
92
- }
93
- }
94
- exports.McpToolAnnotation = McpToolAnnotation;
95
- class McpPromptAnnotation extends McpAnnotation {
96
- _name;
97
- _template;
98
- constructor(target, serviceName, name, template) {
99
- super(target, serviceName);
100
- this._name = name;
101
- this._template = template;
102
- }
103
- get name() {
104
- return this._name;
105
- }
106
- get template() {
107
- return this._template;
108
- }
109
- }
110
- exports.McpPromptAnnotation = McpPromptAnnotation;
111
- function parseAnnotations(services) {
112
- const annotations = [];
113
- for (const serviceName of Object.keys(services)) {
114
- const srv = services[serviceName];
115
- if (srv.name === "CatalogService") {
116
- utils_1.LOGGER.debug("SERVICE: ", srv.model.definitions);
117
- }
118
- const entities = srv.entities;
119
- const operations = srv.operations; // Refers to action and function imports
120
- // Find entities
121
- for (const entityName of Object.keys(entities)) {
122
- const target = entities[entityName];
123
- const res = findEntityAnnotations(target, entityName, srv);
124
- if (target.actions) {
125
- const bound = parseBoundOperations(target.actions, entityName, target, srv);
126
- if (bound && bound.length > 0) {
127
- annotations.push(...bound);
128
- }
129
- }
130
- if (!res)
131
- continue;
132
- annotations.push(res);
133
- }
134
- // Find operations
135
- for (const operationName of Object.keys(operations)) {
136
- const op = operations[operationName];
137
- const res = findOperationAnnotations(op, operationName, srv);
138
- if (!res)
139
- continue;
140
- annotations.push(res);
141
- }
142
- }
143
- const result = formatAnnotations(annotations);
144
- return result;
145
- }
146
- function formatAnnotations(annotationList) {
147
- const result = new Map();
148
- for (const annotation of annotationList) {
149
- if (annotation.operation) {
150
- if (!annotation.annotations[exports.McpAnnotations.MCP_TOOL_NAME] ||
151
- !annotation.annotations[exports.McpAnnotations.MCP_TOOL_DESCRIPTION]) {
152
- utils_1.LOGGER.error(`Invalid annotation found for operation`, annotation);
153
- throw new Error(`Invalid annotations for operation '${annotation.operation}'`);
154
- }
155
- else if (typeof annotation.annotations[exports.McpAnnotations.MCP_TOOL_NAME] !==
156
- "string" ||
157
- typeof annotation.annotations[exports.McpAnnotations.MCP_TOOL_DESCRIPTION] !==
158
- "string") {
159
- utils_1.LOGGER.error("Invalid data for annotations", annotation);
160
- throw new Error(`Invalid annotation data for operation '${annotation.operation}'`);
161
- }
162
- const entry = new McpToolAnnotation(annotation.annotations[exports.McpAnnotations.MCP_TOOL_NAME], annotation.annotations[exports.McpAnnotations.MCP_TOOL_DESCRIPTION], annotation.operation, annotation.serviceName, mapOperationInput(annotation.context), // TODO: Parse the parameters from the context and place them in the class
163
- annotation.entityKey, annotation.operationKind, annotation.keyTypeMap);
164
- result.set(entry.target, entry);
165
- continue;
166
- }
167
- if (!annotation.entityKey) {
168
- utils_1.LOGGER.error("Invalid entry", annotation);
169
- throw new Error(`Invalid annotated entry found with no target`);
170
- }
171
- if (!annotation.annotations[exports.McpAnnotations.MCP_RESOURCE]) {
172
- utils_1.LOGGER.error("No valid annotations found for entry", annotation);
173
- throw new Error(`Invalid annotations for entry target: '${annotation.entityKey}'`);
174
- }
175
- const includeAll = annotation.annotations[exports.McpAnnotations.MCP_RESOURCE] === true;
176
- const functionalities = Array.isArray(annotation.annotations[exports.McpAnnotations.MCP_RESOURCE])
177
- ? new Set(annotation.annotations[exports.McpAnnotations.MCP_RESOURCE])
178
- : DEFAULT_ALL_RESOURCE_OPTIONS;
179
- const entry = new McpResourceAnnotation(annotation.entityKey, annotation.serviceName, includeAll, functionalities, (0, utils_1.parseEntityElements)(annotation.context));
180
- result.set(entry.target, entry);
181
- }
182
- utils_1.LOGGER.debug("Formatted annotations", result);
183
- return result;
184
- }
185
- function findEntityAnnotations(entry, entityKey, service) {
186
- const annotations = findAnnotations(entry);
187
- return Object.keys(annotations).length > 0
188
- ? {
189
- serviceName: service.name,
190
- annotations: annotations,
191
- entityKey: entityKey,
192
- context: entry,
193
- }
194
- : undefined;
195
- }
196
- function findOperationAnnotations(operation, operationName, service) {
197
- const annotations = findAnnotations(operation);
198
- return Object.keys(annotations).length > 0
199
- ? {
200
- serviceName: service.name,
201
- annotations: annotations,
202
- operation: operationName,
203
- operationKind: operation.kind,
204
- context: operation,
205
- }
206
- : undefined;
207
- }
208
- function parseBoundOperations(operations, entityKey, entity, service) {
209
- const res = new Array();
210
- for (const [operationName, operation] of Object.entries(operations)) {
211
- const annotation = findBoundOperationAnnotations(operation, operationName, entityKey, service);
212
- if (!annotation)
213
- continue;
214
- annotation.keyTypeMap = new Map();
215
- for (const [k, v] of Object.entries(entity.keys)) {
216
- if (!v.type) {
217
- utils_1.LOGGER.error("Invalid key type", k);
218
- throw new Error("Invalid key type found for bound operation");
219
- }
220
- annotation.keyTypeMap.set(k, v.type.replace("cds.", ""));
221
- }
222
- res.push(annotation);
223
- }
224
- return res;
225
- }
226
- function findBoundOperationAnnotations(operation, operationName, entityKey, service) {
227
- const annotations = findAnnotations(operation);
228
- return Object.keys(annotations).length > 0
229
- ? {
230
- serviceName: service.name,
231
- annotations: annotations,
232
- operation: operationName,
233
- operationKind: operation.kind,
234
- entityKey: entityKey,
235
- context: operation,
236
- }
237
- : undefined;
238
- }
239
- function findAnnotations(entry) {
240
- const annotations = {};
241
- for (const [k, v] of Object.entries(entry)) {
242
- if (!k.includes(exports.McpAnnotationKey))
243
- continue;
244
- annotations[k] = v;
245
- }
246
- return annotations;
247
- }
248
- function mapOperationInput(ctx) {
249
- const params = ctx["params"];
250
- if (!params)
251
- return undefined;
252
- const result = new Map();
253
- for (const [k, v] of Object.entries(params)) {
254
- result.set(k, v.type.replace("cds.", ""));
255
- }
256
- return result.size > 0 ? result : undefined;
257
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
package/lib/auth/mock.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
package/lib/auth/types.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,156 +0,0 @@
1
- "use strict";
2
- /**
3
- * Custom URI template implementation that fixes the MCP SDK's broken
4
- * URI template matching for grouped query parameters.
5
- *
6
- * This is duck typing implementation of the ResourceTemplate class.
7
- * See @modelcontextprotocol/sdk/server/mcp.js
8
- *
9
- * This is only a temporary solution, as we should use the official implementation from the SDK
10
- * Upon the SDK being fixed, we should switch over to that implementation.
11
- */
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.CustomResourceTemplate = exports.CustomUriTemplate = void 0;
14
- // TODO: Get rid of 'any' typing
15
- /**
16
- * Custom URI template class that properly handles grouped query parameters
17
- * in the format {?param1,param2,param3}
18
- */
19
- class CustomUriTemplate {
20
- template;
21
- baseUri = "";
22
- queryParams = [];
23
- constructor(template) {
24
- this.template = template;
25
- this.parseTemplate();
26
- }
27
- toString() {
28
- return this.template;
29
- }
30
- parseTemplate() {
31
- // Extract base URI and query parameters from template
32
- // Template format: odata://CatalogService/books{?filter,orderby,select,skip,top}
33
- const queryTemplateMatch = this.template.match(/^([^{]+)\{\?([^}]+)\}$/);
34
- if (!queryTemplateMatch) {
35
- // No query parameters, treat as static URI
36
- this.baseUri = this.template;
37
- this.queryParams = [];
38
- return;
39
- }
40
- this.baseUri = queryTemplateMatch[1];
41
- this.queryParams = queryTemplateMatch[2]
42
- .split(",")
43
- .map((param) => param.trim())
44
- .filter((param) => param.length > 0);
45
- }
46
- /**
47
- * Matches a URI against this template and extracts variables
48
- * @param uri The URI to match
49
- * @returns Object with extracted variables or null if no match
50
- */
51
- match(uri) {
52
- // Check if base URI matches
53
- if (!uri.startsWith(this.baseUri)) {
54
- return null;
55
- }
56
- // Extract query string
57
- const queryStart = uri.indexOf("?");
58
- if (queryStart === -1) {
59
- // No query parameters in URI
60
- if (this.queryParams.length === 0) {
61
- return {}; // Static URI match
62
- }
63
- // Template expects query params but URI has none - still valid for optional params
64
- return {};
65
- }
66
- const queryString = uri.substring(queryStart + 1);
67
- const queryPairs = queryString.split("&");
68
- const extractedVars = {};
69
- // Parse query parameters with strict validation
70
- for (const pair of queryPairs) {
71
- const equalIndex = pair.indexOf("=");
72
- if (equalIndex > 0) {
73
- const key = pair.substring(0, equalIndex);
74
- const value = pair.substring(equalIndex + 1);
75
- if (key && value !== undefined) {
76
- const decodedKey = decodeURIComponent(key);
77
- const decodedValue = decodeURIComponent(value);
78
- // SECURITY: Reject entire URI if ANY unauthorized parameter is present
79
- if (!this.queryParams.includes(decodedKey)) {
80
- return null; // Unauthorized parameter found - reject entire URI
81
- }
82
- extractedVars[decodedKey] = decodedValue;
83
- }
84
- }
85
- else if (pair.trim().length > 0) {
86
- // Handle malformed parameters (missing = or empty key)
87
- // SECURITY: Reject malformed query parameters
88
- return null;
89
- }
90
- }
91
- // For static templates (no parameters allowed), reject any query string
92
- if (this.queryParams.length === 0 && queryString.trim().length > 0) {
93
- return null;
94
- }
95
- return extractedVars;
96
- }
97
- /**
98
- * Expands the template with given variables
99
- * @param variables Object containing variable values
100
- * @returns Expanded URI string
101
- */
102
- expand(variables) {
103
- if (this.queryParams.length === 0) {
104
- return this.baseUri;
105
- }
106
- const queryPairs = [];
107
- for (const param of this.queryParams) {
108
- const value = variables[param];
109
- if (value !== undefined && value !== null && value !== "") {
110
- queryPairs.push(`${encodeURIComponent(param)}=${encodeURIComponent(value)}`);
111
- }
112
- }
113
- if (queryPairs.length === 0) {
114
- return this.baseUri;
115
- }
116
- return `${this.baseUri}?${queryPairs.join("&")}`;
117
- }
118
- /**
119
- * Gets the variable names from the template
120
- */
121
- get variableNames() {
122
- return [...this.queryParams];
123
- }
124
- }
125
- exports.CustomUriTemplate = CustomUriTemplate;
126
- /**
127
- * Custom ResourceTemplate that uses our CustomUriTemplate for proper URI matching
128
- * Duck-types the MCP SDK's ResourceTemplate interface for compatibility
129
- */
130
- class CustomResourceTemplate {
131
- _uriTemplate;
132
- _callbacks;
133
- constructor(uriTemplate, callbacks) {
134
- this._callbacks = callbacks;
135
- this._uriTemplate = new CustomUriTemplate(uriTemplate);
136
- }
137
- /**
138
- * Gets the URI template pattern - must match MCP SDK interface
139
- */
140
- get uriTemplate() {
141
- return this._uriTemplate;
142
- }
143
- /**
144
- * Gets the list callback, if one was provided
145
- */
146
- get listCallback() {
147
- return this._callbacks.list;
148
- }
149
- /**
150
- * Gets the callback for completing a specific URI template variable
151
- */
152
- completeCallback(variable) {
153
- return this._callbacks.complete?.[variable];
154
- }
155
- }
156
- exports.CustomResourceTemplate = CustomResourceTemplate;
package/lib/types.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });