norn-cli 3.0.0 → 3.1.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/NOW.md +70 -20
- package/README.md +74 -2
- package/demos/agent-workbench/norn.config.json +1 -0
- package/demos/mcp-ticket-testing/README.md +114 -0
- package/demos/mcp-ticket-testing/agents.nornagent +77 -0
- package/demos/mcp-ticket-testing/contracts/test-run.schema.json +31 -0
- package/demos/mcp-ticket-testing/expectations/proj-142.md +12 -0
- package/demos/mcp-ticket-testing/fixtures/proj-142.json +13 -0
- package/demos/mcp-ticket-testing/prompts/backend-tester.md +12 -0
- package/demos/mcp-ticket-testing/prompts/frontend-tester.md +14 -0
- package/demos/mcp-ticket-testing/prompts/reporter.md +10 -0
- package/demos/mcp-ticket-testing/servers/browser-server.js +133 -0
- package/demos/mcp-ticket-testing/servers/house-server.js +125 -0
- package/demos/mcp-ticket-testing/tickets.norn +32 -0
- package/dist/cli.js +1324 -459
- package/package.json +3 -3
- package/playground/ai.norn +8 -2
- package/playground/ai_orchastration.nornagent +20 -1
- package/playground/knowedge_base/nexus_system_prompt.md +1 -1
- package/schemas/norn.config.schema.json +12 -0
- package/CHANGELOG.md +0 -1529
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* "House" — the custom MCP server a team writes for its own product.
|
|
5
|
+
*
|
|
6
|
+
* Deterministic on purpose: the demo is about declaring a server and granting it, not about
|
|
7
|
+
* what any particular tool does. Everything here is in-memory and reset per process.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
11
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
12
|
+
const { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
|
|
13
|
+
|
|
14
|
+
const state = {
|
|
15
|
+
tenant: 'acme',
|
|
16
|
+
auditLog: [],
|
|
17
|
+
orders: [
|
|
18
|
+
{ id: 'A-1001', tenant: 'acme', total: 42.5, status: 'placed' },
|
|
19
|
+
{ id: 'A-1002', tenant: 'acme', total: 18.0, status: 'shipped' }
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const TOOLS = [
|
|
24
|
+
{
|
|
25
|
+
name: 'getFixtureUser',
|
|
26
|
+
description: 'Return the seeded test user for the current tenant.',
|
|
27
|
+
inputSchema: { type: 'object', additionalProperties: false, properties: {} }
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'resetTenant',
|
|
31
|
+
description: 'Reset the tenant to its seeded state before a test run.',
|
|
32
|
+
inputSchema: { type: 'object', additionalProperties: false, properties: {} }
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'callEndpoint',
|
|
36
|
+
description: 'Call a backend endpoint and return its status and body.',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
properties: {
|
|
41
|
+
method: { type: 'string', description: 'HTTP method, e.g. GET or POST.' },
|
|
42
|
+
path: { type: 'string', description: 'Endpoint path, e.g. /orders/A-1001.' }
|
|
43
|
+
},
|
|
44
|
+
required: ['method', 'path']
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'readAuditLog',
|
|
49
|
+
description: 'Return the audit entries written during this run, oldest first.',
|
|
50
|
+
inputSchema: { type: 'object', additionalProperties: false, properties: {} }
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'queryDb',
|
|
54
|
+
description: 'Read rows from a seeded table by name.',
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
properties: { table: { type: 'string', description: 'Table to read, e.g. orders.' } },
|
|
59
|
+
required: ['table']
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
function structured(value) {
|
|
65
|
+
return { content: [{ type: 'text', text: JSON.stringify(value) }], structuredContent: value };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function callEndpoint(args) {
|
|
69
|
+
const path = String(args.path || '');
|
|
70
|
+
const method = String(args.method || 'GET').toUpperCase();
|
|
71
|
+
state.auditLog.push({ at: state.auditLog.length + 1, method, path });
|
|
72
|
+
|
|
73
|
+
const orderMatch = path.match(/^\/orders\/([A-Za-z0-9-]+)$/);
|
|
74
|
+
if (orderMatch) {
|
|
75
|
+
const order = state.orders.find(item => item.id === orderMatch[1]);
|
|
76
|
+
return order
|
|
77
|
+
? { status: 200, body: order }
|
|
78
|
+
: { status: 404, body: { error: 'order not found' } };
|
|
79
|
+
}
|
|
80
|
+
if (path === '/orders') {
|
|
81
|
+
return { status: 200, body: { orders: state.orders } };
|
|
82
|
+
}
|
|
83
|
+
return { status: 404, body: { error: 'no such endpoint' } };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function main() {
|
|
87
|
+
const server = new Server(
|
|
88
|
+
{ name: 'norn-demo-house', version: '1.0.0' },
|
|
89
|
+
{ capabilities: { tools: {} } }
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
93
|
+
|
|
94
|
+
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
95
|
+
const args = request.params.arguments || {};
|
|
96
|
+
switch (request.params.name) {
|
|
97
|
+
case 'getFixtureUser':
|
|
98
|
+
return structured({ id: 'u-1', email: 'tester@example.com', tenant: state.tenant });
|
|
99
|
+
case 'resetTenant':
|
|
100
|
+
state.auditLog = [];
|
|
101
|
+
return structured({ tenant: state.tenant, reset: true });
|
|
102
|
+
case 'callEndpoint':
|
|
103
|
+
return structured(callEndpoint(args));
|
|
104
|
+
case 'readAuditLog':
|
|
105
|
+
return structured({ entries: state.auditLog });
|
|
106
|
+
case 'queryDb':
|
|
107
|
+
return structured({
|
|
108
|
+
table: args.table,
|
|
109
|
+
rows: args.table === 'orders' ? state.orders : []
|
|
110
|
+
});
|
|
111
|
+
default:
|
|
112
|
+
return {
|
|
113
|
+
isError: true,
|
|
114
|
+
content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await server.connect(new StdioServerTransport());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
main().catch(error => {
|
|
123
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "./agents.nornagent"
|
|
2
|
+
|
|
3
|
+
# Prove both servers are reachable without spending anything on a model.
|
|
4
|
+
# These steps resolve the same aliases the agents use, so a failure here points at the server
|
|
5
|
+
# rather than at an agent — this is the loop to develop a server against.
|
|
6
|
+
test sequence ServersUp
|
|
7
|
+
var browserTools = run mcp list Browser
|
|
8
|
+
assert browserTools.length > 0
|
|
9
|
+
|
|
10
|
+
var houseTools = run mcp list House
|
|
11
|
+
assert houseTools.length > 0
|
|
12
|
+
|
|
13
|
+
var user = run mcp call House getFixtureUser()
|
|
14
|
+
assert user.structuredContent.email == "tester@example.com"
|
|
15
|
+
end sequence
|
|
16
|
+
|
|
17
|
+
# The application this whole slice was designed against: a ticket arrives, two agents test it
|
|
18
|
+
# from different sides with different tools, a third writes it up, and a judge rules on whether
|
|
19
|
+
# the expected test cases were actually covered.
|
|
20
|
+
test sequence TicketPROJ142
|
|
21
|
+
var ticket = run readJson "./fixtures/proj-142.json"
|
|
22
|
+
|
|
23
|
+
var frontend = run FrontendTester ticket
|
|
24
|
+
var backend = run BackendTester ticket
|
|
25
|
+
# An agent's input is a variable or a string — there is no object-literal form — so the two
|
|
26
|
+
# runs are interpolated into one prompt.
|
|
27
|
+
var report = run Reporter "Frontend run:\n{{frontend.body}}\n\nBackend run:\n{{backend.body}}"
|
|
28
|
+
|
|
29
|
+
print "QA report" | "{{report.text}}"
|
|
30
|
+
|
|
31
|
+
judge report with Reviewer expects file expectations/proj-142.md
|
|
32
|
+
end sequence
|