Rename the supply agent everywhere it is named
Its Keycloak client, audience scope, client-secret key, workflow names and file names, and the A2A card. Kafka moved first, under its own account and consumer group. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
{
|
||||
"name": "Supply \u2014 periodic reorder proposal",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "days",
|
||||
"triggerAtHour": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "every-morning",
|
||||
"name": "Every morning",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://keycloak.sttlab.local/realms/classic-models/protocol/openid-connect/token",
|
||||
"sendBody": true,
|
||||
"contentType": "form-urlencoded",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "grant_type",
|
||||
"value": "client_credentials"
|
||||
},
|
||||
{
|
||||
"name": "client_id",
|
||||
"value": "classic-models-agent-supply"
|
||||
},
|
||||
{
|
||||
"name": "client_secret",
|
||||
"value": "={{ $env.SUPPLY_CLIENT_SECRET }}"
|
||||
},
|
||||
{
|
||||
"name": "scope",
|
||||
"value": "openid mcp-catalog-audience token-exchange-broker-audience"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "machine-token",
|
||||
"name": "Machine token",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
220,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://catalog-dev.sttlab.local/catalog-mcp",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "=Bearer {{ $json.access_token }}"
|
||||
},
|
||||
{
|
||||
"name": "Accept",
|
||||
"value": "application/json, text/event-stream"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"initialize\", \"params\": {\"protocolVersion\": \"2025-06-18\", \"capabilities\": {}, \"clientInfo\": {\"name\": \"supply-periodic\", \"version\": \"0.1.0\"}}}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"fullResponse": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "mcp-initialize",
|
||||
"name": "MCP initialize",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
440,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://catalog-dev.sttlab.local/catalog-mcp",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "=Bearer {{ $('Machine token').item.json.access_token }}"
|
||||
},
|
||||
{
|
||||
"name": "Accept",
|
||||
"value": "application/json, text/event-stream"
|
||||
},
|
||||
{
|
||||
"name": "mcp-session-id",
|
||||
"value": "={{ $json.headers['mcp-session-id'] }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "{\"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\", \"params\": {\"name\": \"list-products\", \"arguments\": {\"query_page\": 1, \"query_page_size\": 200}}}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "list-products",
|
||||
"name": "List products",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
660,
|
||||
0
|
||||
],
|
||||
"alwaysOutputData": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Which references need a purchase order, and why.\n//\n// The proposal is computed and goes no further, because there is nowhere for it\n// to go: the classicmodels schema has no purchase order, no supplier entity \u2014\n// `productvendor` is a string on the product \u2014 and the catalog's only write\n// tool is create-product, which is the Catalogue agent's business, not this\n// one's. So this run escalates rather than writing, which is also what an\n// autonomous agent meeting a decision it cannot take is supposed to do.\nconst THRESHOLD = 50;\nconst COVER = 3; // reorder up to three times the threshold\n\nconst item = $input.first();\nconst raw = item?.json ?? {};\nconst body = typeof raw === 'string' ? raw : (raw.data ?? JSON.stringify(raw));\nconst line = String(body).split('\\n').find((l) => l.startsWith('data:'));\nif (!line) {\n return [{ json: { proposals: [], escalate: true,\n reason: 'The catalog did not answer in the expected shape.' }}];\n}\n\nlet rpc;\ntry { rpc = JSON.parse(line.slice(5).trim()); }\ncatch (e) { return [{ json: { proposals: [], escalate: true,\n reason: \"The catalog's reply could not be read.\" }}]; }\n\n// A tool failure is a successful result carrying isError, not a JSON-RPC error.\nif (rpc.error || rpc.result?.isError) {\n const why = rpc.error?.message ?? rpc.result?.content?.[0]?.text ?? 'unknown';\n return [{ json: { proposals: [], escalate: true,\n reason: `The catalog refused the read: ${why}` }}];\n}\n\nconst page = JSON.parse(rpc.result.content[0].text);\nconst products = page.results ?? [];\nconst scanned = products.length;\nconst total = page.count ?? scanned;\n\nconst proposals = products\n .filter((p) => Number(p.quantityinstock) < THRESHOLD)\n .map((p) => ({\n productcode: p.productcode,\n productname: p.productname,\n vendor: p.productvendor,\n on_hand: Number(p.quantityinstock),\n propose_order: THRESHOLD * COVER - Number(p.quantityinstock),\n }));\n\nreturn [{ json: {\n proposals,\n scanned,\n // Loud when the scan was partial: a proposal set computed from part of the\n // catalog looks exactly like one computed from all of it.\n complete: scanned >= total,\n escalate: proposals.length > 0 || scanned < total,\n reason: scanned < total\n ? `Scanned ${scanned} of ${total} references; the rest were not read.`\n : (proposals.length\n ? `${proposals.length} reference(s) below the threshold of ${THRESHOLD}. `\n + 'Nowhere to record a purchase order \u2014 the domain has no such concept \u2014 '\n + 'so this needs a human.'\n : 'Nothing below the threshold.'),\n}}];\n"
|
||||
},
|
||||
"id": "propose-reorders",
|
||||
"name": "Propose reorders",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
880,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Every morning": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Machine token",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Machine token": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "MCP initialize",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"MCP initialize": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "List products",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"List products": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Propose reorders",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user