feat(foundry): add managed data consumers and agent settings
This commit is contained in:
+101
-4
@@ -114,6 +114,9 @@ function toolText(payload, isError = false) {
|
||||
function normalizeMcpError(error) {
|
||||
const code = String(error?.message || "foundry_operation_failed");
|
||||
if (code === "application_not_found") return { code, status: 404 };
|
||||
if (/^(?:data_product_|resync_required$|stream_cursor_invalid$)/.test(code) && Number.isInteger(error?.statusCode)) {
|
||||
return { code, status: error.statusCode };
|
||||
}
|
||||
if (code.startsWith("invalid_") || code.startsWith("unknown_") || code.startsWith("map_") || code.endsWith("_required")) return { code, status: 400 };
|
||||
if (code.startsWith("duplicate_") || code.includes("conflict") || code.includes("mismatch")) return { code, status: 409 };
|
||||
return { code: "foundry_operation_failed", status: 500 };
|
||||
@@ -268,6 +271,87 @@ const tools = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_plan_map_data_product_consumer",
|
||||
title: "Plan Map data product consumer",
|
||||
description: "Validate an approved Map data-product binding, its target-scoped server reader and versioned Foundry consumer policy. Returns a safe deterministic plan; it never returns a capability or endpoint.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["applicationId", "pageId", "bindingId"],
|
||||
properties: {
|
||||
applicationId: { type: "string" },
|
||||
pageId: { type: "string" },
|
||||
bindingId: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_apply_map_data_product_consumer",
|
||||
title: "Apply Map data product consumer",
|
||||
description: "Apply an exact consumer plan and commit a scoped snapshot into server-owned Foundry state. Active page viewers then share one upstream durable patch stream.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["applicationId", "pageId", "bindingId", "planId", "idempotencyKey"],
|
||||
properties: {
|
||||
applicationId: { type: "string" },
|
||||
pageId: { type: "string" },
|
||||
bindingId: { type: "string" },
|
||||
planId: { type: "string" },
|
||||
idempotencyKey: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_get_map_data_product_consumer_status",
|
||||
title: "Get Map data product consumer status",
|
||||
description: "Read safe persisted cursor, subject/status counters, reconnect diagnostics and viewer/upstream counts for one approved binding. No raw fact attributes, capability or endpoint are returned.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["applicationId", "pageId", "bindingId"],
|
||||
properties: {
|
||||
applicationId: { type: "string" },
|
||||
pageId: { type: "string" },
|
||||
bindingId: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_accept_map_data_product_consumer",
|
||||
title: "Accept Map data product consumer",
|
||||
description: "Run a bounded lifecycle acceptance lease against the shared server consumer and verify persisted snapshot/cursor, subject identity, optional new patches, deduplication and secret boundary.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["applicationId", "pageId", "bindingId"],
|
||||
properties: {
|
||||
applicationId: { type: "string" },
|
||||
pageId: { type: "string" },
|
||||
bindingId: { type: "string" },
|
||||
timeoutMs: { type: "integer", minimum: 250, maximum: 30000 },
|
||||
minSubjectCount: { type: "integer", minimum: 0, maximum: 5000 },
|
||||
minPatchCount: { type: "integer", minimum: 0, maximum: 1000 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_rollback_map_data_product_consumer",
|
||||
title: "Rollback Map data product consumer",
|
||||
description: "Stop one server-owned consumer without deleting its last safe snapshot. Re-applying a fresh exact plan resumes it.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["applicationId", "pageId", "bindingId", "idempotencyKey"],
|
||||
properties: {
|
||||
applicationId: { type: "string" },
|
||||
pageId: { type: "string" },
|
||||
bindingId: { type: "string" },
|
||||
idempotencyKey: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function toolMap(operations) {
|
||||
@@ -280,6 +364,11 @@ function toolMap(operations) {
|
||||
foundry_add_page_instance: (input, actor) => operations.addPageInstance(input, actor),
|
||||
foundry_upsert_map_pin_binding: (input, actor) => operations.upsertMapPinBinding(input, actor),
|
||||
foundry_upsert_map_data_product_binding: (input, actor) => operations.upsertMapDataProductBinding(input, actor),
|
||||
foundry_plan_map_data_product_consumer: (input) => operations.planMapDataProductConsumer(input),
|
||||
foundry_apply_map_data_product_consumer: (input, actor) => operations.applyMapDataProductConsumer(input, actor),
|
||||
foundry_get_map_data_product_consumer_status: (input) => operations.getMapDataProductConsumerStatus(input),
|
||||
foundry_accept_map_data_product_consumer: (input) => operations.acceptMapDataProductConsumer(input),
|
||||
foundry_rollback_map_data_product_consumer: (input, actor) => operations.rollbackMapDataProductConsumer(input, actor),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -292,8 +381,14 @@ export async function handleFoundryMcpRequest(request, response, options) {
|
||||
return;
|
||||
}
|
||||
if (!originAllowed(request, allowedOrigins)) return sendJson(response, 403, { error: "mcp_origin_not_allowed" });
|
||||
if (!config.capabilitySecret) return sendJson(response, 503, { error: "foundry_mcp_not_configured" });
|
||||
const actor = actorFromMcpCapability(bearerToken(request), config);
|
||||
const token = bearerToken(request);
|
||||
let actor = config.capabilitySecret ? actorFromMcpCapability(token, config) : null;
|
||||
if (!actor && typeof options.authenticateAgentToken === "function") {
|
||||
actor = await options.authenticateAgentToken(token, { check: true });
|
||||
}
|
||||
if (!config.capabilitySecret && typeof options.authenticateAgentToken !== "function") {
|
||||
return sendJson(response, 503, { error: "foundry_mcp_not_configured" });
|
||||
}
|
||||
if (!actor) return sendJson(response, 401, { error: "mcp_unauthorized" });
|
||||
|
||||
let message;
|
||||
@@ -310,8 +405,8 @@ export async function handleFoundryMcpRequest(request, response, options) {
|
||||
return sendJson(response, 200, mcpResult(id, {
|
||||
protocolVersion: MCP_PROTOCOL_VERSION,
|
||||
capabilities: { tools: { listChanged: false } },
|
||||
serverInfo: { name: "nodedc_module_foundry", version: "0.1.0" },
|
||||
instructions: "NDC Module Foundry edits application instances only. Page Library is read-only. Application deletion is unavailable.",
|
||||
serverInfo: { name: "nodedc_module_foundry", version: "0.2.0" },
|
||||
instructions: "NDC Module Foundry edits application instances and controls approved server-owned data-product consumers. Page Library is read-only. Application deletion is unavailable. Provider endpoints and credentials are never MCP inputs or outputs.",
|
||||
}));
|
||||
}
|
||||
if (message.method === "notifications/initialized") return sendJson(response, 202, {});
|
||||
@@ -383,6 +478,8 @@ export async function handleFoundryEntitlementRequest(request, response, options
|
||||
"foundry.page-instance.create",
|
||||
"foundry.map-pin.upsert",
|
||||
"foundry.map-data-product.upsert",
|
||||
"foundry.map-data-product-consumer.read",
|
||||
"foundry.map-data-product-consumer.lifecycle",
|
||||
],
|
||||
};
|
||||
if (!accessAllowed) {
|
||||
|
||||
Reference in New Issue
Block a user