@noodleseed/one 0.137.0 → 0.139.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/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/assistant-ops.d.ts.map +1 -1
- package/dist/commands/assistant-ops.js +30 -1
- package/dist/commands/assistant-ops.js.map +1 -1
- package/dist/commands/deploy-first-flow.d.ts +2 -0
- package/dist/commands/deploy-first-flow.d.ts.map +1 -1
- package/dist/commands/deploy-first-flow.js +46 -14
- package/dist/commands/deploy-first-flow.js.map +1 -1
- package/dist/commands/deploy-ops.d.ts.map +1 -1
- package/dist/commands/deploy-ops.js +27 -1
- package/dist/commands/deploy-ops.js.map +1 -1
- package/dist/commands/deploy-preflight.d.ts +1 -0
- package/dist/commands/deploy-preflight.d.ts.map +1 -1
- package/dist/commands/deploy-preflight.js +1 -0
- package/dist/commands/deploy-preflight.js.map +1 -1
- package/dist/commands/deploy-status-ops.d.ts +1 -0
- package/dist/commands/deploy-status-ops.d.ts.map +1 -1
- package/dist/commands/deploy-status-ops.js +59 -4
- package/dist/commands/deploy-status-ops.js.map +1 -1
- package/dist/commands/deployments-ops.d.ts.map +1 -1
- package/dist/commands/deployments-ops.js +2 -0
- package/dist/commands/deployments-ops.js.map +1 -1
- package/dist/commands/diagnostics-ops.d.ts +1 -0
- package/dist/commands/diagnostics-ops.d.ts.map +1 -1
- package/dist/commands/diagnostics-ops.js +3 -0
- package/dist/commands/diagnostics-ops.js.map +1 -1
- package/dist/commands/import.d.ts +4 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +198 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/project-setup.d.ts +1 -1
- package/dist/commands/project-setup.d.ts.map +1 -1
- package/dist/commands/project-setup.js +2 -48
- package/dist/commands/project-setup.js.map +1 -1
- package/dist/commands/service-ops.d.ts.map +1 -1
- package/dist/commands/service-ops.js +44 -51
- package/dist/commands/service-ops.js.map +1 -1
- package/dist/deploy.d.ts +3 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +4 -0
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-import.d.ts +22 -0
- package/dist/mcp-import.d.ts.map +1 -0
- package/dist/mcp-import.js +91 -0
- package/dist/mcp-import.js.map +1 -0
- package/dist/self-host-init.d.ts +23 -0
- package/dist/self-host-init.d.ts.map +1 -0
- package/dist/self-host-init.js +315 -0
- package/dist/self-host-init.js.map +1 -0
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +6 -6
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +11 -2
- package/node_modules/@noodle-borg/agent-kit/dist/skill-connect-refs.js +31 -1
- package/node_modules/@noodle-borg/agent-kit/dist/skill-content.js +3 -2
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +6 -7
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.d.ts +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-guide.js +35 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +6 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +5 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +37 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +4 -0
- package/node_modules/@noodle-borg/{service/dist/assistant-model-stream.js → assistant-gateway/dist/model-stream.js} +34 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.d.ts +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.d.ts +4 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +6 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +11 -5
- package/node_modules/@noodle-borg/assistant-gateway/package.json +5 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +10 -2
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +4 -0
- package/node_modules/@noodle-borg/authoring/dist/connections.js +1 -0
- package/node_modules/@noodle-borg/authoring/dist/connectors.d.ts +25 -3
- package/node_modules/@noodle-borg/authoring/dist/connectors.js +82 -7
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +2 -2
- package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/model-tool-visibility.js +33 -0
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +5 -0
- package/node_modules/@noodle-borg/authoring/dist/server.js +7 -2
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-account.js +13 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +17 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-bootstrap-authoring.js +30 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-deployment.js +14 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +1 -7
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +20 -8
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -8
- package/node_modules/@noodle-borg/connector-defs/dist/call-graph.js +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/compile-mcp.js +95 -0
- package/node_modules/@noodle-borg/connector-defs/dist/compile.js +4 -0
- package/node_modules/@noodle-borg/connector-defs/dist/customer-routing.js +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/schema.d.ts +642 -3
- package/node_modules/@noodle-borg/connector-defs/dist/schema.js +143 -1
- package/node_modules/@noodle-borg/connector-http/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/connector-http/dist/index.js +1 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/config.js +76 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/egress.js +125 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/failure.js +48 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/index.d.ts +3 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/index.js +2 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/mcp-connector.d.ts +12 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/mcp-connector.js +125 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/result.js +55 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/types.d.ts +52 -0
- package/node_modules/@noodle-borg/connector-http/dist/mcp/types.js +10 -0
- package/node_modules/@noodle-borg/connector-http/package.json +4 -1
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/rollback-deployment.d.ts +61 -0
- package/node_modules/@noodle-borg/{service/dist/operations → control-plane/dist}/rollback-deployment.js +3 -2
- package/node_modules/@noodle-borg/control-plane/dist/rollback-result.d.ts +19 -0
- package/node_modules/@noodle-borg/control-plane/package.json +3 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/contracts.js +4 -0
- package/node_modules/@noodle-borg/developer-mcp/dist/widgets/app-overview.js +1 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/widgets/deployment-detail.js +1 -1
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/deployment-automation.d.ts +1 -1
- package/node_modules/@noodle-borg/module/dist/endpoint-url.d.ts +11 -0
- package/node_modules/@noodle-borg/module/dist/endpoint-url.js +20 -0
- package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/index.js +1 -0
- package/node_modules/@noodle-borg/observability/dist/assistant-usage.js +217 -0
- package/node_modules/@noodle-borg/observability/dist/index.js +2 -0
- package/node_modules/@noodle-borg/{service → observability}/dist/request-event-metrics.js +5 -2
- package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +6 -0
- package/node_modules/@noodle-borg/observability/dist/request-events.js +2 -0
- package/node_modules/@noodle-borg/openapi-import/dist/index.js +1 -0
- package/node_modules/@noodle-borg/openapi-import/dist/mcp/index.js +4 -0
- package/node_modules/@noodle-borg/openapi-import/dist/mcp/probe.js +165 -0
- package/node_modules/@noodle-borg/openapi-import/dist/mcp/render.js +99 -0
- package/node_modules/@noodle-borg/openapi-import/dist/mcp/snapshot.js +290 -0
- package/node_modules/@noodle-borg/openapi-import/package.json +6 -2
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +2 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +1 -0
- package/node_modules/@noodle-borg/service/dist/assistant-bindings.js +3 -1
- package/node_modules/@noodle-borg/service/dist/developer-mcp/control-plane-adapter.js +3 -3
- package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +2 -1
- package/node_modules/@noodle-borg/service/dist/registry-access.js +73 -24
- package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +5 -2
- package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +5 -9
- package/node_modules/@noodle-borg/service/dist/registry-helpers.js +4 -18
- package/node_modules/@noodle-borg/service/dist/registry-rollback.js +3 -1
- package/node_modules/@noodle-borg/service/dist/registry-status.js +4 -1
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -4
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +11 -16
- package/node_modules/@noodle-borg/service/dist/registry.d.ts +8 -5
- package/node_modules/@noodle-borg/service/dist/registry.js +8 -5
- package/node_modules/@noodle-borg/service/dist/routes/access.js +19 -1
- package/node_modules/@noodle-borg/service/dist/routes/analytics-dispatch.js +17 -1
- package/node_modules/@noodle-borg/service/dist/routes/analytics.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +81 -64
- package/node_modules/@noodle-borg/service/dist/routes/assistant-doctor.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +28 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +18 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-turn.js +6 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-usage.js +42 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +20 -3
- package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +49 -4
- package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +11 -1
- package/node_modules/@noodle-borg/service/dist/routes/endpoint-enrichment.js +2 -1
- package/node_modules/@noodle-borg/service/dist/routes/paths.js +4 -0
- package/node_modules/@noodle-borg/service/dist/routes/rollback.js +1 -1
- package/node_modules/@noodle-borg/service/dist/service.js +6 -0
- package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +2 -5
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +9 -21
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +2 -5
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -20
- package/node_modules/@noodle-borg/service/dist/store/records.js +39 -0
- package/node_modules/@noodle-borg/service/dist/store.d.ts +21 -10
- package/node_modules/@noodle-borg/service/package.json +5 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +120 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +123 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +4 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +15 -1
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Client, SdkHttpError, StreamableHTTPClientTransport, } from '@modelcontextprotocol/client';
|
|
2
|
+
import { guardedFetch } from '@noodle-borg/connector-http';
|
|
3
|
+
import { snapshotFromTools } from './snapshot.js';
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
5
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 6 * 1024 * 1024;
|
|
6
|
+
const MAX_TIMEOUT_MS = 300_000;
|
|
7
|
+
export async function probeMcpServer(options) {
|
|
8
|
+
const endpoint = safeEndpoint(options.endpoint);
|
|
9
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
10
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS) {
|
|
11
|
+
throw new Error(`MCP import timeout must be between 1 and ${MAX_TIMEOUT_MS} milliseconds`);
|
|
12
|
+
}
|
|
13
|
+
const maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
|
|
14
|
+
if (!Number.isSafeInteger(maxResponseBytes) ||
|
|
15
|
+
maxResponseBytes < 1 ||
|
|
16
|
+
maxResponseBytes > DEFAULT_MAX_RESPONSE_BYTES) {
|
|
17
|
+
throw new Error(`MCP import response limit must be between 1 and ${DEFAULT_MAX_RESPONSE_BYTES} bytes`);
|
|
18
|
+
}
|
|
19
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
20
|
+
const fetch = importFetch(endpoint.origin, options.headers ?? {}, maxResponseBytes, options.fetch);
|
|
21
|
+
const client = new Client({ name: 'noodle-mcp-import', version: '1.0.0' }, {
|
|
22
|
+
capabilities: {},
|
|
23
|
+
versionNegotiation: { mode: protocolMode(options.protocol) },
|
|
24
|
+
inputRequired: { autoFulfill: false },
|
|
25
|
+
listMaxPages: 32,
|
|
26
|
+
});
|
|
27
|
+
const transport = new StreamableHTTPClientTransport(endpoint, {
|
|
28
|
+
fetch,
|
|
29
|
+
onInsufficientScope: 'throw',
|
|
30
|
+
});
|
|
31
|
+
const closeTransportOnAbort = () => {
|
|
32
|
+
void transport.close().catch(() => undefined);
|
|
33
|
+
};
|
|
34
|
+
signal.addEventListener('abort', closeTransportOnAbort, { once: true });
|
|
35
|
+
if (signal.aborted)
|
|
36
|
+
closeTransportOnAbort();
|
|
37
|
+
let tools;
|
|
38
|
+
try {
|
|
39
|
+
await client.connect(transport, { signal, timeout: timeoutMs });
|
|
40
|
+
const result = await client.listTools(undefined, {
|
|
41
|
+
signal,
|
|
42
|
+
timeout: timeoutMs,
|
|
43
|
+
cacheMode: 'bypass',
|
|
44
|
+
});
|
|
45
|
+
tools = result.tools.map((tool) => ({
|
|
46
|
+
name: tool.name,
|
|
47
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
48
|
+
inputSchema: tool.inputSchema,
|
|
49
|
+
...(tool.outputSchema === undefined ? {} : { outputSchema: tool.outputSchema }),
|
|
50
|
+
...(tool.annotations === undefined ? {} : { annotations: tool.annotations }),
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (signal.aborted)
|
|
55
|
+
throw new Error('MCP import probe timed out');
|
|
56
|
+
if (SdkHttpError.isInstance(error)) {
|
|
57
|
+
throw new Error(`MCP import probe rejected with HTTP ${error.status}`);
|
|
58
|
+
}
|
|
59
|
+
throw new Error('MCP import probe failed');
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
signal.removeEventListener('abort', closeTransportOnAbort);
|
|
63
|
+
await client.close().catch(() => undefined);
|
|
64
|
+
}
|
|
65
|
+
return snapshotFromTools({
|
|
66
|
+
endpoint: options.endpoint,
|
|
67
|
+
name: options.name,
|
|
68
|
+
...(options.prefix === undefined ? {} : { prefix: options.prefix }),
|
|
69
|
+
...(options.auth === undefined ? {} : { auth: options.auth }),
|
|
70
|
+
tools,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function importFetch(allowedOrigin, configuredHeaders, maxBytes, injected) {
|
|
74
|
+
const safeHeaders = validatedHeaders(configuredHeaders);
|
|
75
|
+
return async (input, init = {}) => {
|
|
76
|
+
const url = new URL(input);
|
|
77
|
+
if (url.origin !== allowedOrigin)
|
|
78
|
+
throw new Error('MCP import egress origin rejected');
|
|
79
|
+
const headers = new Headers(init.headers);
|
|
80
|
+
for (const [name, value] of Object.entries(safeHeaders))
|
|
81
|
+
headers.set(name, value);
|
|
82
|
+
const response = injected === undefined
|
|
83
|
+
? await guardedFetch(url, { ...init, headers, redirect: 'manual' })
|
|
84
|
+
: await injected(url, { ...init, headers, redirect: 'manual' });
|
|
85
|
+
if (response.status >= 300 && response.status <= 399) {
|
|
86
|
+
throw new Error('MCP import redirect rejected');
|
|
87
|
+
}
|
|
88
|
+
return boundedResponse(response, maxBytes);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function boundedResponse(response, maxBytes) {
|
|
92
|
+
const length = Number(response.headers.get('content-length') ?? '0');
|
|
93
|
+
if (length > maxBytes)
|
|
94
|
+
throw new Error('MCP import response exceeds the size limit');
|
|
95
|
+
if (response.body === null)
|
|
96
|
+
return response;
|
|
97
|
+
const reader = response.body.getReader();
|
|
98
|
+
let size = 0;
|
|
99
|
+
return new Response(new ReadableStream({
|
|
100
|
+
async pull(controller) {
|
|
101
|
+
const chunk = await reader.read();
|
|
102
|
+
if (chunk.done) {
|
|
103
|
+
controller.close();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
size += chunk.value.byteLength;
|
|
107
|
+
if (size > maxBytes) {
|
|
108
|
+
await reader.cancel().catch(() => undefined);
|
|
109
|
+
controller.error(new Error('MCP import response exceeds the size limit'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
controller.enqueue(chunk.value);
|
|
113
|
+
},
|
|
114
|
+
async cancel(reason) {
|
|
115
|
+
await reader.cancel(reason).catch(() => undefined);
|
|
116
|
+
},
|
|
117
|
+
}), { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
118
|
+
}
|
|
119
|
+
function safeEndpoint(value) {
|
|
120
|
+
const url = new URL(value);
|
|
121
|
+
if (url.username !== '' || url.password !== '' || url.hash !== '') {
|
|
122
|
+
throw new Error('MCP endpoint contains forbidden URL components');
|
|
123
|
+
}
|
|
124
|
+
const loopback = url.protocol === 'http:' &&
|
|
125
|
+
(url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]');
|
|
126
|
+
if (url.protocol !== 'https:' && !loopback)
|
|
127
|
+
throw new Error('MCP endpoint must use HTTPS');
|
|
128
|
+
return url;
|
|
129
|
+
}
|
|
130
|
+
function validatedHeaders(input) {
|
|
131
|
+
if (Object.keys(input).length > 32)
|
|
132
|
+
throw new Error('MCP import accepts at most 32 headers');
|
|
133
|
+
const headers = Object.create(null);
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
for (const [name, value] of Object.entries(input)) {
|
|
136
|
+
const lower = name.toLowerCase();
|
|
137
|
+
if (seen.has(lower))
|
|
138
|
+
throw new Error(`MCP import header "${name}" is duplicated`);
|
|
139
|
+
seen.add(lower);
|
|
140
|
+
if (lower === 'host' ||
|
|
141
|
+
lower === 'cookie' ||
|
|
142
|
+
lower === 'content-type' ||
|
|
143
|
+
lower === 'content-length' ||
|
|
144
|
+
lower.startsWith('mcp-')) {
|
|
145
|
+
throw new Error(`MCP import header "${name}" is transport-owned`);
|
|
146
|
+
}
|
|
147
|
+
if (value.length > 8_192 || /[\r\n]/.test(value)) {
|
|
148
|
+
throw new Error(`MCP import header "${name}" has an invalid value`);
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
new Headers({ [name]: value });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
throw new Error(`MCP import header "${name}" is invalid`);
|
|
155
|
+
}
|
|
156
|
+
headers[lower] = value;
|
|
157
|
+
}
|
|
158
|
+
return headers;
|
|
159
|
+
}
|
|
160
|
+
function protocolMode(value) {
|
|
161
|
+
if (value === 'modern')
|
|
162
|
+
return { pin: '2026-07-28' };
|
|
163
|
+
return value ?? 'auto';
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=probe.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export function renderMcpServerSource(snapshot) {
|
|
2
|
+
const needsSecret = snapshot.auth !== undefined;
|
|
3
|
+
const imports = ['annotations', 'connector', 'server', 'tool', 'variable'];
|
|
4
|
+
if (needsSecret)
|
|
5
|
+
imports.push('secret');
|
|
6
|
+
const operations = snapshot.tools.map(renderOperation).join(',\n ');
|
|
7
|
+
const tools = snapshot.tools.map(renderTool).join(',\n ');
|
|
8
|
+
const auth = snapshot.auth === undefined ? '' : `\n auth: ${renderAuth(snapshot.auth)},`;
|
|
9
|
+
return `import { ${imports.sort().join(', ')} } from '@noodleseed/one';
|
|
10
|
+
|
|
11
|
+
const upstream = connector(${quote(snapshot.connectorId)})
|
|
12
|
+
.version('1.0.0')
|
|
13
|
+
.mcp({
|
|
14
|
+
endpoint: variable(${quote(snapshot.endpointVariable)}),
|
|
15
|
+
allowedOrigins: [variable(${quote(snapshot.originVariable)})],${auth}
|
|
16
|
+
operations: {
|
|
17
|
+
${operations}
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export default server(${quote(snapshot.connectorId)}, {
|
|
22
|
+
title: ${quote(title(snapshot.connectorId))},
|
|
23
|
+
version: '1.0.0',
|
|
24
|
+
use: { upstream },
|
|
25
|
+
}, [
|
|
26
|
+
${tools}
|
|
27
|
+
]);
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
function renderOperation(tool) {
|
|
31
|
+
return `${tool.operationName}: {
|
|
32
|
+
type: ${quote(tool.operationType)},
|
|
33
|
+
tool: ${quote(tool.upstreamName)},
|
|
34
|
+
result: ${quote(tool.result)},
|
|
35
|
+
input: ${json(tool.inputSchema, 8)},
|
|
36
|
+
output: ${json(tool.outputSchema, 8)},
|
|
37
|
+
}`;
|
|
38
|
+
}
|
|
39
|
+
function renderTool(item) {
|
|
40
|
+
const annotations = item.operationType === 'read'
|
|
41
|
+
? 'annotations.readOnly({ openWorld: true })'
|
|
42
|
+
: `annotations.openAction({ destructive: ${String(item.destructive)}, confirm: true })`;
|
|
43
|
+
const properties = schemaProperties(item.outputSchema);
|
|
44
|
+
const input = schemaProperties(item.inputSchema)
|
|
45
|
+
.map((property) => `${quote(property)}: input[${quote(property)}]`)
|
|
46
|
+
.join(', ');
|
|
47
|
+
const operationCall = `connectors.upstream.${item.operationName}({ ${input} })`;
|
|
48
|
+
const outputSchema = properties.length === 0
|
|
49
|
+
? {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: { result: item.outputSchema },
|
|
52
|
+
required: ['result'],
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
}
|
|
55
|
+
: item.outputSchema;
|
|
56
|
+
const fulfil = properties.length === 0
|
|
57
|
+
? `({ input, connectors }) => ({ result: ${operationCall}.raw })`
|
|
58
|
+
: `({ input, connectors }) => {
|
|
59
|
+
const result = ${operationCall};
|
|
60
|
+
return {
|
|
61
|
+
${properties.map((property) => ` ${quote(property)}: result[${quote(property)}],`).join('\n')}
|
|
62
|
+
};
|
|
63
|
+
}`;
|
|
64
|
+
return `tool(${quote(item.operationName)}, {
|
|
65
|
+
title: ${quote(title(item.operationName))},
|
|
66
|
+
description: ${quote(item.description)},
|
|
67
|
+
annotations: ${annotations},
|
|
68
|
+
input: ${json(item.inputSchema, 4)},
|
|
69
|
+
output: ${json(outputSchema, 4)},
|
|
70
|
+
fulfil: ${fulfil},
|
|
71
|
+
})`;
|
|
72
|
+
}
|
|
73
|
+
function schemaProperties(schema) {
|
|
74
|
+
const properties = schema.properties;
|
|
75
|
+
if (typeof properties !== 'object' || properties === null || Array.isArray(properties))
|
|
76
|
+
return [];
|
|
77
|
+
return Object.keys(properties).sort();
|
|
78
|
+
}
|
|
79
|
+
function renderAuth(auth) {
|
|
80
|
+
if (auth.kind === 'bearer') {
|
|
81
|
+
return `{ kind: 'bearer', secret: secret(${quote(auth.secretRef)}) }`;
|
|
82
|
+
}
|
|
83
|
+
return `{ kind: 'apiKey', header: ${quote(auth.header ?? 'x-api-key')}, secret: secret(${quote(auth.secretRef)}) }`;
|
|
84
|
+
}
|
|
85
|
+
function json(value, indentation) {
|
|
86
|
+
const pad = ' '.repeat(indentation);
|
|
87
|
+
return JSON.stringify(value, null, 2).replace(/\n/g, `\n${pad}`);
|
|
88
|
+
}
|
|
89
|
+
function quote(value) {
|
|
90
|
+
return JSON.stringify(value);
|
|
91
|
+
}
|
|
92
|
+
function title(value) {
|
|
93
|
+
return value
|
|
94
|
+
.split('_')
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.map((part) => `${part[0]?.toUpperCase() ?? ''}${part.slice(1)}`)
|
|
97
|
+
.join(' ');
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { normalizeOperationIoSchema } from '@noodle-borg/compiler';
|
|
2
|
+
const NAME = /^[a-z0-9_]+$/;
|
|
3
|
+
const MAX_TOOLS = 256;
|
|
4
|
+
const MAX_UPSTREAM_NAME_LENGTH = 256;
|
|
5
|
+
const MAX_SCHEMA_DEPTH = 64;
|
|
6
|
+
const MAX_SCHEMA_NODES = 50_000;
|
|
7
|
+
export function snapshotFromTools(input) {
|
|
8
|
+
if (input.tools.length > MAX_TOOLS) {
|
|
9
|
+
throw new Error(`upstream MCP server exposes more than ${MAX_TOOLS} tools`);
|
|
10
|
+
}
|
|
11
|
+
const connectorId = safeBaseName(input.name);
|
|
12
|
+
const safePrefix = input.prefix === undefined ? undefined : safeBaseName(input.prefix);
|
|
13
|
+
const prefix = safePrefix === undefined ? '' : `${safePrefix}_`;
|
|
14
|
+
const warnings = [];
|
|
15
|
+
const counts = new Map();
|
|
16
|
+
const tools = [];
|
|
17
|
+
const discovered = input.tools.map((tool) => ({
|
|
18
|
+
...tool,
|
|
19
|
+
name: upstreamName(tool.name),
|
|
20
|
+
}));
|
|
21
|
+
for (const tool of discovered.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
22
|
+
const base = `${prefix}${safeBaseName(tool.name)}`;
|
|
23
|
+
const count = (counts.get(base) ?? 0) + 1;
|
|
24
|
+
counts.set(base, count);
|
|
25
|
+
const operationName = count === 1 ? base : `${base}_${count}`;
|
|
26
|
+
if (operationName !== tool.name) {
|
|
27
|
+
warnings.push(`mapped upstream tool "${tool.name}" to operation "${operationName}"`);
|
|
28
|
+
}
|
|
29
|
+
const inputSchema = objectSchema(tool.inputSchema, `${tool.name}.inputSchema`);
|
|
30
|
+
const result = tool.outputSchema === undefined ? 'text' : 'structured';
|
|
31
|
+
const outputSchema = tool.outputSchema === undefined
|
|
32
|
+
? textOutputSchema()
|
|
33
|
+
: objectSchema(tool.outputSchema, `${tool.name}.outputSchema`);
|
|
34
|
+
const operationType = 'action';
|
|
35
|
+
const destructive = true;
|
|
36
|
+
if (tool.annotations?.readOnlyHint === true) {
|
|
37
|
+
warnings.push(`tool "${tool.name}" claims readOnlyHint; imported as a confirmed action until the author verifies its behavior`);
|
|
38
|
+
}
|
|
39
|
+
else if (tool.annotations?.readOnlyHint === undefined) {
|
|
40
|
+
warnings.push(`tool "${tool.name}" has no readOnlyHint; imported conservatively as a confirmed action`);
|
|
41
|
+
}
|
|
42
|
+
tools.push({
|
|
43
|
+
upstreamName: tool.name,
|
|
44
|
+
operationName,
|
|
45
|
+
description: cleanDescription(tool.description, tool.name),
|
|
46
|
+
operationType,
|
|
47
|
+
destructive,
|
|
48
|
+
inputSchema,
|
|
49
|
+
outputSchema,
|
|
50
|
+
result,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
formatVersion: 1,
|
|
55
|
+
connectorId,
|
|
56
|
+
...(safePrefix === undefined ? {} : { prefix: safePrefix }),
|
|
57
|
+
endpoint: canonicalEndpoint(input.endpoint),
|
|
58
|
+
endpointVariable: `${constantName(connectorId)}_MCP_ENDPOINT`,
|
|
59
|
+
originVariable: `${constantName(connectorId)}_MCP_ORIGIN`,
|
|
60
|
+
...(input.auth === undefined ? {} : { auth: input.auth }),
|
|
61
|
+
tools,
|
|
62
|
+
warnings: [...warnings].sort(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function upstreamName(value) {
|
|
66
|
+
if (value.length === 0 || value.length > MAX_UPSTREAM_NAME_LENGTH || /\p{Cc}/u.test(value)) {
|
|
67
|
+
throw new Error(`upstream MCP tool name must contain 1-${MAX_UPSTREAM_NAME_LENGTH} non-control characters`);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
export function diffSnapshots(before, after) {
|
|
72
|
+
const lines = [];
|
|
73
|
+
const prior = new Map(before.tools.map((tool) => [tool.upstreamName, tool]));
|
|
74
|
+
const next = new Map(after.tools.map((tool) => [tool.upstreamName, tool]));
|
|
75
|
+
for (const name of [...new Set([...prior.keys(), ...next.keys()])].sort()) {
|
|
76
|
+
const left = prior.get(name);
|
|
77
|
+
const right = next.get(name);
|
|
78
|
+
if (left === undefined)
|
|
79
|
+
lines.push(`additive: added tool: ${name}`);
|
|
80
|
+
else if (right === undefined)
|
|
81
|
+
lines.push(`breaking: removed tool: ${name}`);
|
|
82
|
+
else if (stableJson(left) !== stableJson(right)) {
|
|
83
|
+
const metadataOnly = stableJson({ ...left, description: '' }) === stableJson({ ...right, description: '' });
|
|
84
|
+
lines.push(`${metadataOnly ? 'metadata-only' : 'breaking'}: changed tool: ${name}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (before.endpoint !== after.endpoint)
|
|
88
|
+
lines.push('breaking: changed endpoint');
|
|
89
|
+
if (stableJson(before.auth) !== stableJson(after.auth))
|
|
90
|
+
lines.push('breaking: changed auth');
|
|
91
|
+
return { changed: lines.length > 0, lines };
|
|
92
|
+
}
|
|
93
|
+
/** Parse the system-owned drift snapshot without trusting edited local JSON. */
|
|
94
|
+
export function parseMcpImportSnapshot(value) {
|
|
95
|
+
if (!isRecord(value) || value.formatVersion !== 1) {
|
|
96
|
+
throw new Error('MCP import snapshot has an unsupported format');
|
|
97
|
+
}
|
|
98
|
+
const connectorId = requiredString(value.connectorId, 'connectorId');
|
|
99
|
+
if (safeBaseName(connectorId) !== connectorId) {
|
|
100
|
+
throw new Error('MCP import snapshot connectorId is invalid');
|
|
101
|
+
}
|
|
102
|
+
const prefix = optionalString(value.prefix, 'prefix');
|
|
103
|
+
if (prefix !== undefined && safeBaseName(prefix) !== prefix) {
|
|
104
|
+
throw new Error('MCP import snapshot prefix is invalid');
|
|
105
|
+
}
|
|
106
|
+
if (!Array.isArray(value.tools) || value.tools.length > MAX_TOOLS) {
|
|
107
|
+
throw new Error(`MCP import snapshot tools must contain at most ${MAX_TOOLS} entries`);
|
|
108
|
+
}
|
|
109
|
+
const tools = value.tools.map((candidate, index) => {
|
|
110
|
+
if (!isRecord(candidate))
|
|
111
|
+
throw new Error(`MCP import snapshot tools[${index}] is invalid`);
|
|
112
|
+
const upstream = upstreamName(requiredString(candidate.upstreamName, `tools[${index}].upstreamName`));
|
|
113
|
+
const operationName = requiredString(candidate.operationName, `tools[${index}].operationName`);
|
|
114
|
+
if (!NAME.test(operationName) || operationName.length > 128) {
|
|
115
|
+
throw new Error(`MCP import snapshot tools[${index}].operationName is invalid`);
|
|
116
|
+
}
|
|
117
|
+
const description = requiredString(candidate.description, `tools[${index}].description`);
|
|
118
|
+
if (description.length > 2_000) {
|
|
119
|
+
throw new Error(`MCP import snapshot tools[${index}].description is too long`);
|
|
120
|
+
}
|
|
121
|
+
if (candidate.operationType !== 'read' && candidate.operationType !== 'action') {
|
|
122
|
+
throw new Error(`MCP import snapshot tools[${index}].operationType is invalid`);
|
|
123
|
+
}
|
|
124
|
+
if (typeof candidate.destructive !== 'boolean') {
|
|
125
|
+
throw new Error(`MCP import snapshot tools[${index}].destructive is invalid`);
|
|
126
|
+
}
|
|
127
|
+
if (candidate.result !== 'structured' && candidate.result !== 'text') {
|
|
128
|
+
throw new Error(`MCP import snapshot tools[${index}].result is invalid`);
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
upstreamName: upstream,
|
|
132
|
+
operationName,
|
|
133
|
+
description,
|
|
134
|
+
operationType: candidate.operationType,
|
|
135
|
+
destructive: candidate.destructive,
|
|
136
|
+
inputSchema: objectSchema(candidate.inputSchema, `tools[${index}].inputSchema`),
|
|
137
|
+
outputSchema: objectSchema(candidate.outputSchema, `tools[${index}].outputSchema`),
|
|
138
|
+
result: candidate.result,
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
if (!Array.isArray(value.warnings) || value.warnings.length > MAX_TOOLS * 2) {
|
|
142
|
+
throw new Error('MCP import snapshot warnings are invalid');
|
|
143
|
+
}
|
|
144
|
+
const warnings = value.warnings.map((warning, index) => {
|
|
145
|
+
const parsed = requiredString(warning, `warnings[${index}]`);
|
|
146
|
+
if (parsed.length > 2_000)
|
|
147
|
+
throw new Error(`MCP import snapshot warnings[${index}] is too long`);
|
|
148
|
+
return parsed;
|
|
149
|
+
});
|
|
150
|
+
const endpoint = canonicalEndpoint(requiredString(value.endpoint, 'endpoint'));
|
|
151
|
+
const endpointVariable = `${constantName(connectorId)}_MCP_ENDPOINT`;
|
|
152
|
+
const originVariable = `${constantName(connectorId)}_MCP_ORIGIN`;
|
|
153
|
+
if (value.endpointVariable !== endpointVariable || value.originVariable !== originVariable) {
|
|
154
|
+
throw new Error('MCP import snapshot managed binding names are invalid');
|
|
155
|
+
}
|
|
156
|
+
const auth = parseAuth(value.auth);
|
|
157
|
+
return {
|
|
158
|
+
formatVersion: 1,
|
|
159
|
+
connectorId,
|
|
160
|
+
...(prefix === undefined ? {} : { prefix }),
|
|
161
|
+
endpoint,
|
|
162
|
+
endpointVariable,
|
|
163
|
+
originVariable,
|
|
164
|
+
...(auth === undefined ? {} : { auth }),
|
|
165
|
+
tools,
|
|
166
|
+
warnings,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function parseAuth(value) {
|
|
170
|
+
if (value === undefined)
|
|
171
|
+
return undefined;
|
|
172
|
+
if (!isRecord(value) || (value.kind !== 'bearer' && value.kind !== 'apiKey')) {
|
|
173
|
+
throw new Error('MCP import snapshot auth is invalid');
|
|
174
|
+
}
|
|
175
|
+
const secretRef = requiredString(value.secretRef, 'auth.secretRef');
|
|
176
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(secretRef)) {
|
|
177
|
+
throw new Error('MCP import snapshot auth.secretRef is invalid');
|
|
178
|
+
}
|
|
179
|
+
if (value.kind === 'bearer')
|
|
180
|
+
return { kind: 'bearer', secretRef };
|
|
181
|
+
const header = requiredString(value.header, 'auth.header');
|
|
182
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(header)) {
|
|
183
|
+
throw new Error('MCP import snapshot auth.header is invalid');
|
|
184
|
+
}
|
|
185
|
+
return { kind: 'apiKey', header, secretRef };
|
|
186
|
+
}
|
|
187
|
+
function requiredString(value, path) {
|
|
188
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
189
|
+
throw new Error(`MCP import snapshot ${path} is invalid`);
|
|
190
|
+
}
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
function optionalString(value, path) {
|
|
194
|
+
if (value === undefined)
|
|
195
|
+
return undefined;
|
|
196
|
+
return requiredString(value, path);
|
|
197
|
+
}
|
|
198
|
+
function objectSchema(value, path) {
|
|
199
|
+
inspectSchema(value, path);
|
|
200
|
+
if (!isRecord(value))
|
|
201
|
+
throw new Error(`${path} must be a JSON Schema object`);
|
|
202
|
+
const type = value.type;
|
|
203
|
+
if (type !== 'object' && !(Array.isArray(type) && type.includes('object'))) {
|
|
204
|
+
throw new Error(`${path} must declare an object top-level type`);
|
|
205
|
+
}
|
|
206
|
+
if (value.additionalProperties !== undefined && typeof value.additionalProperties !== 'boolean') {
|
|
207
|
+
throw new Error(`${path}.additionalProperties must be boolean at the top level`);
|
|
208
|
+
}
|
|
209
|
+
return normalizeOperationIoSchema(value);
|
|
210
|
+
}
|
|
211
|
+
function inspectSchema(value, path) {
|
|
212
|
+
let nodes = 0;
|
|
213
|
+
const visit = (node, depth) => {
|
|
214
|
+
nodes += 1;
|
|
215
|
+
if (nodes > MAX_SCHEMA_NODES)
|
|
216
|
+
throw new Error(`${path} exceeds the schema node limit`);
|
|
217
|
+
if (depth > MAX_SCHEMA_DEPTH)
|
|
218
|
+
throw new Error(`${path} exceeds the schema depth limit`);
|
|
219
|
+
if (Array.isArray(node)) {
|
|
220
|
+
for (const item of node)
|
|
221
|
+
visit(item, depth + 1);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (!isRecord(node))
|
|
225
|
+
return;
|
|
226
|
+
if (typeof node.$ref === 'string' && !node.$ref.startsWith('#')) {
|
|
227
|
+
throw new Error(`${path} contains an external $ref, which is never fetched`);
|
|
228
|
+
}
|
|
229
|
+
for (const child of Object.values(node))
|
|
230
|
+
visit(child, depth + 1);
|
|
231
|
+
};
|
|
232
|
+
visit(value, 0);
|
|
233
|
+
}
|
|
234
|
+
function safeBaseName(value) {
|
|
235
|
+
const separated = value
|
|
236
|
+
.normalize('NFKD')
|
|
237
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
238
|
+
.toLowerCase()
|
|
239
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
240
|
+
.replace(/^_+|_+$/g, '')
|
|
241
|
+
.replace(/_+/g, '_');
|
|
242
|
+
const safe = /^[a-z]/.test(separated) ? separated : `tool_${separated}`;
|
|
243
|
+
if (!NAME.test(safe) || safe.length === 0)
|
|
244
|
+
throw new Error(`cannot derive a safe name from "${value}"`);
|
|
245
|
+
return safe.slice(0, 96).replace(/_+$/g, '');
|
|
246
|
+
}
|
|
247
|
+
function constantName(value) {
|
|
248
|
+
return value.toUpperCase();
|
|
249
|
+
}
|
|
250
|
+
function cleanDescription(value, name) {
|
|
251
|
+
const cleaned = value
|
|
252
|
+
?.replace(/\p{Cc}/gu, (character) => character === '\n' || character === '\t' ? character : '')
|
|
253
|
+
.trim()
|
|
254
|
+
.slice(0, 2_000);
|
|
255
|
+
return cleaned && cleaned.length > 0 ? cleaned : `Call upstream MCP tool "${name}".`;
|
|
256
|
+
}
|
|
257
|
+
function textOutputSchema() {
|
|
258
|
+
return {
|
|
259
|
+
type: 'object',
|
|
260
|
+
properties: { text: { type: 'string' } },
|
|
261
|
+
required: ['text'],
|
|
262
|
+
additionalProperties: false,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function canonicalEndpoint(value) {
|
|
266
|
+
const url = new URL(value);
|
|
267
|
+
if (url.username !== '' || url.password !== '' || url.hash !== '') {
|
|
268
|
+
throw new Error('MCP endpoint contains forbidden URL components');
|
|
269
|
+
}
|
|
270
|
+
if (url.protocol !== 'https:' &&
|
|
271
|
+
!(url.protocol === 'http:' &&
|
|
272
|
+
(url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]'))) {
|
|
273
|
+
throw new Error('MCP endpoint must use HTTPS');
|
|
274
|
+
}
|
|
275
|
+
return url.toString();
|
|
276
|
+
}
|
|
277
|
+
function stableJson(value) {
|
|
278
|
+
if (Array.isArray(value))
|
|
279
|
+
return `[${value.map(stableJson).join(',')}]`;
|
|
280
|
+
if (!isRecord(value))
|
|
281
|
+
return JSON.stringify(value);
|
|
282
|
+
return `{${Object.keys(value)
|
|
283
|
+
.sort()
|
|
284
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
|
285
|
+
.join(',')}}`;
|
|
286
|
+
}
|
|
287
|
+
function isRecord(value) {
|
|
288
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -22,11 +22,15 @@
|
|
|
22
22
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/client": "2.0.0",
|
|
26
|
+
"@noodle-borg/compiler": "0.0.0",
|
|
27
|
+
"@noodle-borg/connector-http": "0.0.0",
|
|
25
28
|
"yaml": "^2.9.0"
|
|
26
29
|
},
|
|
27
30
|
"devDependencies": {
|
|
28
|
-
"@
|
|
31
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
29
32
|
"@noodle-borg/connector-defs": "0.0.0",
|
|
30
|
-
"@types/node": "^24.13.1"
|
|
33
|
+
"@types/node": "^24.13.1",
|
|
34
|
+
"zod": "^4.4.3"
|
|
31
35
|
}
|
|
32
36
|
}
|
|
@@ -74,6 +74,8 @@ export interface ConnectorCall {
|
|
|
74
74
|
readonly args: Readonly<Record<string, unknown>>;
|
|
75
75
|
/** Managed variables resolved for the deployment scope. */
|
|
76
76
|
readonly env?: Readonly<Record<string, string>>;
|
|
77
|
+
/** Request-scoped cancellation; connectors should propagate it to outbound work. */
|
|
78
|
+
readonly signal?: AbortSignal;
|
|
77
79
|
/**
|
|
78
80
|
* The downstream-scoped credential minted by the broker — never an inbound bearer token
|
|
79
81
|
* (docs/SPEC.md "Auth And Identity").
|
|
@@ -163,6 +163,7 @@ async function invokeOperation(ref, args, sig, toolName, deps, policy, host, env
|
|
|
163
163
|
operation: ref.operation,
|
|
164
164
|
args: connectorArgs,
|
|
165
165
|
env,
|
|
166
|
+
...(deps.signal === undefined ? {} : { signal: deps.signal }),
|
|
166
167
|
credential,
|
|
167
168
|
...(ref.credentialBinding === undefined
|
|
168
169
|
? {}
|
|
@@ -3,7 +3,7 @@ import { missingSecretErrors, missingVariableErrors } from './registry-helpers.j
|
|
|
3
3
|
import { serverAuthSecretBindings } from './server-auth-bindings.js';
|
|
4
4
|
const VARIABLE = /\$\{env\.([A-Za-z0-9_]+)\}/g;
|
|
5
5
|
export function assistantSecretBindings(assistant) {
|
|
6
|
-
return assistant
|
|
6
|
+
return assistant?.model.kind === 'openai-compatible'
|
|
7
7
|
? [
|
|
8
8
|
{
|
|
9
9
|
connectorId: 'server.assistant',
|
|
@@ -17,6 +17,8 @@ export function assistantSecretBindings(assistant) {
|
|
|
17
17
|
export function assistantVariableBindings(assistant) {
|
|
18
18
|
if (!assistant)
|
|
19
19
|
return [];
|
|
20
|
+
if (assistant.model.kind !== 'openai-compatible')
|
|
21
|
+
return [];
|
|
20
22
|
const names = new Set();
|
|
21
23
|
for (const value of [assistant.model.baseUrl, assistant.model.model]) {
|
|
22
24
|
for (const match of value.matchAll(VARIABLE)) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { mcpSubdomainEndpointOptions } from '@noodle-borg/control-plane/portable';
|
|
1
|
+
import { mcpSubdomainEndpointOptions, rollbackDeploymentOperation, } from '@noodle-borg/control-plane/portable';
|
|
2
2
|
import { DeveloperControlPlaneError, } from '@noodle-borg/developer-mcp';
|
|
3
|
-
import {
|
|
4
|
-
import { aggregateRequestEvents } from '../request-event-metrics.js';
|
|
3
|
+
import { aggregateRequestEvents } from '@noodle-borg/observability';
|
|
5
4
|
import { inspectCompiledServiceDeployment, inspectServiceDeployment } from './inspection.js';
|
|
6
5
|
const METRICS_SCAN_LIMIT = 50_000;
|
|
7
6
|
const WINDOWS_MS = {
|
|
@@ -261,6 +260,7 @@ function mapDeployment(deployment) {
|
|
|
261
260
|
serverName: deployment.serverName,
|
|
262
261
|
createdAt: deployment.createdAt,
|
|
263
262
|
accessMode: deployment.accessMode,
|
|
263
|
+
...(deployment.ownerSubject !== undefined ? { ownerSubject: deployment.ownerSubject } : {}),
|
|
264
264
|
...(deployment.endpointUrl !== undefined ? { endpointUrl: deployment.endpointUrl } : {}),
|
|
265
265
|
};
|
|
266
266
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tenantMcpUrl } from '
|
|
1
|
+
import { tenantMcpUrl } from '@noodle-borg/module';
|
|
2
2
|
export async function inspectServiceDeployment(registry, tenant, baseUrl, endpointOptions = {}) {
|
|
3
3
|
const status = await registry.getStatus(tenant, baseUrl, undefined, endpointOptions);
|
|
4
4
|
if (status === undefined)
|
|
@@ -109,6 +109,7 @@ export function inspectCompiledServiceDeployment(deployment, artifact, baseUrl,
|
|
|
109
109
|
? {}
|
|
110
110
|
: { createdByEmail: deployment.createdByEmail }),
|
|
111
111
|
accessMode: deployment.accessMode,
|
|
112
|
+
...(deployment.ownerSubject !== undefined ? { ownerSubject: deployment.ownerSubject } : {}),
|
|
112
113
|
},
|
|
113
114
|
health: { state: 'ready', missingSecrets: [] },
|
|
114
115
|
surface,
|