@pdpp/mcp-server 0.4.0 → 0.5.0
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 +1 -1
- package/src/tools.js +52 -0
package/package.json
CHANGED
package/src/tools.js
CHANGED
|
@@ -156,6 +156,49 @@ class MalformedExpandLimitError extends Error {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
// Thrown when `expand` is requested on a stream whose schema advertises no
|
|
160
|
+
// expand_capabilities. The RS would also reject this with invalid_expand, but
|
|
161
|
+
// the MCP adapter catches it first so the error message names the stream and
|
|
162
|
+
// the canonical fix (consult schema before constructing expand arguments).
|
|
163
|
+
class UnadvertisedExpandError extends Error {
|
|
164
|
+
constructor(stream, relations) {
|
|
165
|
+
const relClause = relations.length
|
|
166
|
+
? `Relations requested: ${relations.join(', ')}. `
|
|
167
|
+
: '';
|
|
168
|
+
super(
|
|
169
|
+
`Stream '${stream}' has no advertised expand_capabilities. ${relClause}` +
|
|
170
|
+
"Consult GET /v1/schema (expand_capabilities) before passing expand; " +
|
|
171
|
+
"unadvertised relations are rejected.",
|
|
172
|
+
);
|
|
173
|
+
this.name = 'UnadvertisedExpandError';
|
|
174
|
+
this.code = 'invalid_expand';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Fetch the compact schema for `stream` and throw UnadvertisedExpandError when
|
|
179
|
+
// no expand_capabilities are advertised. Called only when the caller has
|
|
180
|
+
// already supplied an `expand` argument, so the extra RS round-trip is bounded
|
|
181
|
+
// to expand-using calls. The connection_id is forwarded so multi-source grants
|
|
182
|
+
// resolve cleanly.
|
|
183
|
+
async function assertExpandCapabilities(rs, stream, relations, connectionId) {
|
|
184
|
+
const schemaQuery = { view: 'compact', stream };
|
|
185
|
+
if (connectionId) schemaQuery.connection_id = connectionId;
|
|
186
|
+
const schemaResp = await rs.getJson('/v1/schema', { query: schemaQuery });
|
|
187
|
+
if (!schemaResp.ok) return; // schema unavailable — let RS enforce
|
|
188
|
+
const schemaDoc = unwrapSchemaBody(schemaResp.body);
|
|
189
|
+
const streams = schemaDoc?.streams ?? [];
|
|
190
|
+
// Find any stream row that matches (may be multiple for shared stream names).
|
|
191
|
+
const matchingStream = Array.isArray(streams)
|
|
192
|
+
? streams.find((s) => s && (s.name === stream || s.stream === stream || s.stream_name === stream))
|
|
193
|
+
: null;
|
|
194
|
+
if (!matchingStream) return; // unknown stream — let RS enforce
|
|
195
|
+
const expandCaps = matchingStream.expand_capabilities;
|
|
196
|
+
const hasExpandCaps = Array.isArray(expandCaps) ? expandCaps.length > 0 : Boolean(expandCaps);
|
|
197
|
+
if (!hasExpandCaps) {
|
|
198
|
+
throw new UnadvertisedExpandError(stream, relations);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
159
202
|
// Thrown when a self-contained fetch id embeds one connection while the
|
|
160
203
|
// explicit `connection_id` argument names another. Silently preferring either
|
|
161
204
|
// handle could read the wrong source, so the disagreement is rejected with a
|
|
@@ -433,6 +476,9 @@ export function buildTools({ rs, providerUrl }) {
|
|
|
433
476
|
outputSchema: z.object(READ_OUTPUT_SCHEMA_SHAPE),
|
|
434
477
|
handler: async (args) => {
|
|
435
478
|
const stream = requireSafeName(args?.stream, 'stream');
|
|
479
|
+
if (Array.isArray(args?.expand) && args.expand.length > 0) {
|
|
480
|
+
await assertExpandCapabilities(rs, stream, args.expand, args?.connection_id);
|
|
481
|
+
}
|
|
436
482
|
const query = applyExpandLimitToQuery(
|
|
437
483
|
applyFilterToQuery(pickQuery(args, SUPPORTED_QUERY_KEYS), args?.filter),
|
|
438
484
|
args?.expand_limit,
|
|
@@ -565,6 +611,10 @@ export function buildTools({ rs, providerUrl }) {
|
|
|
565
611
|
if (ref.connectionId && typeof args?.connection_id === 'string' && args.connection_id !== ref.connectionId) {
|
|
566
612
|
throw new ConflictingConnectionIdError(ref.connectionId, args.connection_id);
|
|
567
613
|
}
|
|
614
|
+
if (Array.isArray(args?.expand) && args.expand.length > 0) {
|
|
615
|
+
const connId = args?.connection_id ?? ref.connectionId;
|
|
616
|
+
await assertExpandCapabilities(rs, ref.stream, args.expand, connId);
|
|
617
|
+
}
|
|
568
618
|
const query = applyExpandLimitToQuery(pickQuery(args, SUPPORTED_QUERY_KEYS), args?.expand_limit);
|
|
569
619
|
// A self-contained id carries its own connection scope; forward it so a
|
|
570
620
|
// multi-source grant resolves without a second model-carried handle.
|
|
@@ -2147,4 +2197,6 @@ export const __internal = {
|
|
|
2147
2197
|
toFetchToolResult,
|
|
2148
2198
|
resolveStreamName,
|
|
2149
2199
|
resolveSchemaDetail,
|
|
2200
|
+
assertExpandCapabilities,
|
|
2201
|
+
UnadvertisedExpandError,
|
|
2150
2202
|
};
|