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.
@@ -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