@criterionx/mcp 0.3.3
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/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.js +241 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tomas Maritano
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# @criterionx/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for Criterion decisions. Exposes business rules as MCP tools for use with LLM applications like Claude Desktop.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @criterionx/mcp @criterionx/core zod
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createMcpServer } from "@criterionx/mcp";
|
|
15
|
+
import { defineDecision } from "@criterionx/core";
|
|
16
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
// Define a decision
|
|
20
|
+
const pricingDecision = defineDecision({
|
|
21
|
+
id: "pricing-tier",
|
|
22
|
+
version: "1.0.0",
|
|
23
|
+
inputSchema: z.object({ revenue: z.number() }),
|
|
24
|
+
outputSchema: z.object({ tier: z.string(), discount: z.number() }),
|
|
25
|
+
profileSchema: z.object({
|
|
26
|
+
tiers: z.array(z.object({ min: z.number(), name: z.string(), discount: z.number() }))
|
|
27
|
+
}),
|
|
28
|
+
rules: [
|
|
29
|
+
{
|
|
30
|
+
id: "enterprise",
|
|
31
|
+
when: (ctx, profile) => ctx.revenue >= profile.tiers[2].min,
|
|
32
|
+
emit: (ctx, profile) => ({ tier: profile.tiers[2].name, discount: profile.tiers[2].discount }),
|
|
33
|
+
explain: () => "Revenue qualifies for enterprise tier",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "growth",
|
|
37
|
+
when: (ctx, profile) => ctx.revenue >= profile.tiers[1].min,
|
|
38
|
+
emit: (ctx, profile) => ({ tier: profile.tiers[1].name, discount: profile.tiers[1].discount }),
|
|
39
|
+
explain: () => "Revenue qualifies for growth tier",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "starter",
|
|
43
|
+
when: () => true,
|
|
44
|
+
emit: (ctx, profile) => ({ tier: profile.tiers[0].name, discount: profile.tiers[0].discount }),
|
|
45
|
+
explain: () => "Default starter tier",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Create MCP server
|
|
51
|
+
const mcpServer = createMcpServer({
|
|
52
|
+
name: "my-decisions",
|
|
53
|
+
decisions: [pricingDecision],
|
|
54
|
+
profiles: {
|
|
55
|
+
"pricing-tier": {
|
|
56
|
+
tiers: [
|
|
57
|
+
{ min: 0, name: "Starter", discount: 0 },
|
|
58
|
+
{ min: 100000, name: "Growth", discount: 10 },
|
|
59
|
+
{ min: 1000000, name: "Enterprise", discount: 25 },
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Connect via stdio transport
|
|
66
|
+
const transport = new StdioServerTransport();
|
|
67
|
+
await mcpServer.server.connect(transport);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## MCP Tools
|
|
71
|
+
|
|
72
|
+
The server exposes four MCP tools:
|
|
73
|
+
|
|
74
|
+
### `list_decisions`
|
|
75
|
+
|
|
76
|
+
List all registered decisions with their metadata.
|
|
77
|
+
|
|
78
|
+
**Input:** None
|
|
79
|
+
|
|
80
|
+
**Output:**
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"decisions": [
|
|
84
|
+
{
|
|
85
|
+
"id": "pricing-tier",
|
|
86
|
+
"version": "1.0.0",
|
|
87
|
+
"description": "Determine pricing tier based on revenue",
|
|
88
|
+
"rulesCount": 3
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `get_decision_schema`
|
|
95
|
+
|
|
96
|
+
Get the JSON schemas for a specific decision.
|
|
97
|
+
|
|
98
|
+
**Input:**
|
|
99
|
+
- `decisionId` (string): The ID of the decision
|
|
100
|
+
|
|
101
|
+
**Output:**
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"id": "pricing-tier",
|
|
105
|
+
"version": "1.0.0",
|
|
106
|
+
"inputSchema": { "type": "object", "properties": { "revenue": { "type": "number" } } },
|
|
107
|
+
"outputSchema": { "type": "object", "properties": { "tier": { "type": "string" } } },
|
|
108
|
+
"profileSchema": { ... }
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `evaluate_decision`
|
|
113
|
+
|
|
114
|
+
Evaluate a decision with the given input and optional profile.
|
|
115
|
+
|
|
116
|
+
**Input:**
|
|
117
|
+
- `decisionId` (string): The ID of the decision to evaluate
|
|
118
|
+
- `input` (object): Input data matching the decision's input schema
|
|
119
|
+
- `profile` (object, optional): Profile to use (overrides default)
|
|
120
|
+
|
|
121
|
+
**Output:**
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"status": "OK",
|
|
125
|
+
"data": { "tier": "Growth", "discount": 10 },
|
|
126
|
+
"meta": {
|
|
127
|
+
"decisionId": "pricing-tier",
|
|
128
|
+
"decisionVersion": "1.0.0",
|
|
129
|
+
"matchedRule": "growth",
|
|
130
|
+
"explanation": "Revenue qualifies for growth tier",
|
|
131
|
+
"evaluatedRules": [
|
|
132
|
+
{ "ruleId": "enterprise", "matched": false },
|
|
133
|
+
{ "ruleId": "growth", "matched": true, "explanation": "Revenue qualifies for growth tier" }
|
|
134
|
+
],
|
|
135
|
+
"evaluatedAt": "2024-12-29T22:00:00.000Z"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `explain_result`
|
|
141
|
+
|
|
142
|
+
Get a human-readable explanation of a decision result.
|
|
143
|
+
|
|
144
|
+
**Input:**
|
|
145
|
+
- `result` (object): The evaluation result to explain
|
|
146
|
+
|
|
147
|
+
**Output:**
|
|
148
|
+
```
|
|
149
|
+
Decision: pricing-tier v1.0.0
|
|
150
|
+
Status: OK
|
|
151
|
+
Matched: growth
|
|
152
|
+
Reason: Revenue qualifies for growth tier
|
|
153
|
+
|
|
154
|
+
Evaluation trace:
|
|
155
|
+
✗ enterprise
|
|
156
|
+
✓ growth
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Claude Desktop Configuration
|
|
160
|
+
|
|
161
|
+
Add to `~/.claude/config.json`:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"mcpServers": {
|
|
166
|
+
"criterion": {
|
|
167
|
+
"command": "node",
|
|
168
|
+
"args": ["/path/to/your/criterion-mcp-server.js"]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## API Reference
|
|
175
|
+
|
|
176
|
+
### `createMcpServer(options)`
|
|
177
|
+
|
|
178
|
+
Create a new Criterion MCP server.
|
|
179
|
+
|
|
180
|
+
**Options:**
|
|
181
|
+
- `name` (string, optional): Server name exposed to MCP clients
|
|
182
|
+
- `version` (string, optional): Server version
|
|
183
|
+
- `decisions` (Decision[]): Decisions to expose as MCP tools
|
|
184
|
+
- `profiles` (Record<string, unknown>, optional): Default profiles keyed by decision ID
|
|
185
|
+
|
|
186
|
+
**Returns:** `CriterionMcpServer`
|
|
187
|
+
|
|
188
|
+
### `CriterionMcpServer`
|
|
189
|
+
|
|
190
|
+
The MCP server class.
|
|
191
|
+
|
|
192
|
+
**Properties:**
|
|
193
|
+
- `server`: The underlying MCP server instance (for transport connection)
|
|
194
|
+
- `decisionRegistry`: Map of registered decisions
|
|
195
|
+
- `profileRegistry`: Map of registered profiles
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Decision } from '@criterionx/core';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MCP Server configuration options
|
|
6
|
+
*/
|
|
7
|
+
interface McpServerOptions {
|
|
8
|
+
/** Server name exposed to MCP clients */
|
|
9
|
+
name?: string;
|
|
10
|
+
/** Server version */
|
|
11
|
+
version?: string;
|
|
12
|
+
/** Decisions to expose as MCP tools */
|
|
13
|
+
decisions: Decision<any, any, any>[];
|
|
14
|
+
/** Default profiles for decisions (keyed by decision ID) */
|
|
15
|
+
profiles?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Decision info returned by list_decisions tool
|
|
19
|
+
*/
|
|
20
|
+
interface DecisionListItem {
|
|
21
|
+
id: string;
|
|
22
|
+
version: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
rulesCount: number;
|
|
25
|
+
meta?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Schema response for get_decision_schema tool
|
|
29
|
+
*/
|
|
30
|
+
interface DecisionSchemaResponse {
|
|
31
|
+
id: string;
|
|
32
|
+
version: string;
|
|
33
|
+
inputSchema: Record<string, unknown>;
|
|
34
|
+
outputSchema: Record<string, unknown>;
|
|
35
|
+
profileSchema: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Error codes for MCP tool responses
|
|
39
|
+
*/
|
|
40
|
+
type McpErrorCode = "DECISION_NOT_FOUND" | "MISSING_PROFILE" | "EVALUATION_ERROR" | "EXPLAIN_ERROR";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Criterion MCP Server
|
|
44
|
+
*
|
|
45
|
+
* Exposes Criterion decisions as MCP tools for use with LLM applications.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import { createMcpServer } from "@criterionx/mcp";
|
|
50
|
+
* import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
51
|
+
*
|
|
52
|
+
* const mcpServer = createMcpServer({
|
|
53
|
+
* decisions: [myDecision],
|
|
54
|
+
* profiles: { "my-decision": { threshold: 100 } },
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* const transport = new StdioServerTransport();
|
|
58
|
+
* await mcpServer.server.connect(transport);
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare class CriterionMcpServer {
|
|
62
|
+
private mcpServer;
|
|
63
|
+
private engine;
|
|
64
|
+
private decisions;
|
|
65
|
+
private profiles;
|
|
66
|
+
constructor(options: McpServerOptions);
|
|
67
|
+
private registerTools;
|
|
68
|
+
/**
|
|
69
|
+
* Get the underlying MCP server instance
|
|
70
|
+
*/
|
|
71
|
+
get server(): McpServer;
|
|
72
|
+
/**
|
|
73
|
+
* Get the decision registry (for testing/introspection)
|
|
74
|
+
*/
|
|
75
|
+
get decisionRegistry(): Map<string, Decision<any, any, any>>;
|
|
76
|
+
/**
|
|
77
|
+
* Get the profile registry (for testing/introspection)
|
|
78
|
+
*/
|
|
79
|
+
get profileRegistry(): Map<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a new Criterion MCP server
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { createMcpServer } from "@criterionx/mcp";
|
|
87
|
+
* import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
88
|
+
*
|
|
89
|
+
* const server = createMcpServer({
|
|
90
|
+
* decisions: [myDecision],
|
|
91
|
+
* profiles: { "my-decision": { threshold: 100 } },
|
|
92
|
+
* });
|
|
93
|
+
*
|
|
94
|
+
* const transport = new StdioServerTransport();
|
|
95
|
+
* await server.server.connect(transport);
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
declare function createMcpServer(options: McpServerOptions): CriterionMcpServer;
|
|
99
|
+
|
|
100
|
+
export { CriterionMcpServer, type DecisionListItem, type DecisionSchemaResponse, type McpErrorCode, type McpServerOptions, createMcpServer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
Engine,
|
|
6
|
+
extractDecisionSchema
|
|
7
|
+
} from "@criterionx/core";
|
|
8
|
+
var PACKAGE_VERSION = "0.3.3";
|
|
9
|
+
var CriterionMcpServer = class {
|
|
10
|
+
mcpServer;
|
|
11
|
+
engine;
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
decisions;
|
|
14
|
+
profiles;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.mcpServer = new McpServer({
|
|
17
|
+
name: options.name ?? "criterion-mcp-server",
|
|
18
|
+
version: options.version ?? PACKAGE_VERSION
|
|
19
|
+
});
|
|
20
|
+
this.engine = new Engine();
|
|
21
|
+
this.decisions = /* @__PURE__ */ new Map();
|
|
22
|
+
this.profiles = /* @__PURE__ */ new Map();
|
|
23
|
+
for (const decision of options.decisions) {
|
|
24
|
+
this.decisions.set(decision.id, decision);
|
|
25
|
+
}
|
|
26
|
+
if (options.profiles) {
|
|
27
|
+
for (const [id, profile] of Object.entries(options.profiles)) {
|
|
28
|
+
this.profiles.set(id, profile);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
this.registerTools();
|
|
32
|
+
}
|
|
33
|
+
registerTools() {
|
|
34
|
+
this.mcpServer.tool(
|
|
35
|
+
"list_decisions",
|
|
36
|
+
"List all registered Criterion decisions with their metadata. Returns decision IDs, versions, descriptions, and rule counts.",
|
|
37
|
+
{},
|
|
38
|
+
async () => {
|
|
39
|
+
const decisions = Array.from(
|
|
40
|
+
this.decisions.values()
|
|
41
|
+
).map((d) => ({
|
|
42
|
+
id: d.id,
|
|
43
|
+
version: d.version,
|
|
44
|
+
description: d.meta?.description,
|
|
45
|
+
rulesCount: d.rules.length,
|
|
46
|
+
meta: d.meta
|
|
47
|
+
}));
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: JSON.stringify({ decisions }, null, 2)
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
this.mcpServer.tool(
|
|
59
|
+
"get_decision_schema",
|
|
60
|
+
"Get the JSON schemas for a specific decision. Returns input, output, and profile schemas that define the decision's contract.",
|
|
61
|
+
{
|
|
62
|
+
decisionId: z.string().describe("The ID of the decision to get schemas for")
|
|
63
|
+
},
|
|
64
|
+
async (args) => {
|
|
65
|
+
const decision = this.decisions.get(args.decisionId);
|
|
66
|
+
if (!decision) {
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: JSON.stringify({
|
|
72
|
+
error: "DECISION_NOT_FOUND",
|
|
73
|
+
message: `Decision not found: ${args.decisionId}`,
|
|
74
|
+
availableDecisions: Array.from(this.decisions.keys())
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
isError: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const schema = extractDecisionSchema(decision);
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: JSON.stringify(schema, null, 2)
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
this.mcpServer.tool(
|
|
93
|
+
"evaluate_decision",
|
|
94
|
+
"Evaluate a Criterion decision with the given input and optional profile. Returns the decision result including status, data, and evaluation metadata with full explainability.",
|
|
95
|
+
{
|
|
96
|
+
decisionId: z.string().describe("The ID of the decision to evaluate"),
|
|
97
|
+
input: z.record(z.unknown()).describe("Input data matching the decision's input schema"),
|
|
98
|
+
profile: z.record(z.unknown()).optional().describe("Optional profile to use (overrides default profile)")
|
|
99
|
+
},
|
|
100
|
+
async (args) => {
|
|
101
|
+
const decision = this.decisions.get(args.decisionId);
|
|
102
|
+
if (!decision) {
|
|
103
|
+
return {
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: JSON.stringify({
|
|
108
|
+
error: "DECISION_NOT_FOUND",
|
|
109
|
+
message: `Decision not found: ${args.decisionId}`,
|
|
110
|
+
availableDecisions: Array.from(this.decisions.keys())
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
isError: true
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
let profile = args.profile;
|
|
118
|
+
if (!profile) {
|
|
119
|
+
profile = this.profiles.get(args.decisionId);
|
|
120
|
+
if (!profile) {
|
|
121
|
+
return {
|
|
122
|
+
content: [
|
|
123
|
+
{
|
|
124
|
+
type: "text",
|
|
125
|
+
text: JSON.stringify({
|
|
126
|
+
error: "MISSING_PROFILE",
|
|
127
|
+
message: `No profile provided and no default profile for decision: ${args.decisionId}`
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
isError: true
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const result = this.engine.run(decision, args.input, { profile });
|
|
137
|
+
return {
|
|
138
|
+
content: [
|
|
139
|
+
{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: JSON.stringify(result, null, 2)
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
};
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: "text",
|
|
151
|
+
text: JSON.stringify({
|
|
152
|
+
error: "EVALUATION_ERROR",
|
|
153
|
+
message: err.message
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
isError: true
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
this.mcpServer.tool(
|
|
163
|
+
"explain_result",
|
|
164
|
+
"Get a human-readable explanation of a decision evaluation result. Formats the result metadata, matched rules, and evaluation trace into an easy-to-understand summary.",
|
|
165
|
+
{
|
|
166
|
+
result: z.object({
|
|
167
|
+
status: z.enum(["OK", "NO_MATCH", "INVALID_INPUT", "INVALID_OUTPUT"]),
|
|
168
|
+
data: z.unknown().nullable(),
|
|
169
|
+
meta: z.object({
|
|
170
|
+
decisionId: z.string(),
|
|
171
|
+
decisionVersion: z.string(),
|
|
172
|
+
profileId: z.string().optional(),
|
|
173
|
+
matchedRule: z.string().optional(),
|
|
174
|
+
evaluatedRules: z.array(
|
|
175
|
+
z.object({
|
|
176
|
+
ruleId: z.string(),
|
|
177
|
+
matched: z.boolean(),
|
|
178
|
+
explanation: z.string().optional()
|
|
179
|
+
})
|
|
180
|
+
),
|
|
181
|
+
explanation: z.string(),
|
|
182
|
+
evaluatedAt: z.string()
|
|
183
|
+
})
|
|
184
|
+
}).describe("The evaluation result to explain")
|
|
185
|
+
},
|
|
186
|
+
async (args) => {
|
|
187
|
+
try {
|
|
188
|
+
const explanation = this.engine.explain(args.result);
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: explanation
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: JSON.stringify({
|
|
204
|
+
error: "EXPLAIN_ERROR",
|
|
205
|
+
message: err.message
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
],
|
|
209
|
+
isError: true
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get the underlying MCP server instance
|
|
217
|
+
*/
|
|
218
|
+
get server() {
|
|
219
|
+
return this.mcpServer;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get the decision registry (for testing/introspection)
|
|
223
|
+
*/
|
|
224
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
225
|
+
get decisionRegistry() {
|
|
226
|
+
return this.decisions;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get the profile registry (for testing/introspection)
|
|
230
|
+
*/
|
|
231
|
+
get profileRegistry() {
|
|
232
|
+
return this.profiles;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
function createMcpServer(options) {
|
|
236
|
+
return new CriterionMcpServer(options);
|
|
237
|
+
}
|
|
238
|
+
export {
|
|
239
|
+
CriterionMcpServer,
|
|
240
|
+
createMcpServer
|
|
241
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@criterionx/mcp",
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"description": "MCP server for Criterion decisions - expose business rules as LLM tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"test:coverage": "vitest run --coverage",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"criterion",
|
|
28
|
+
"decision-engine",
|
|
29
|
+
"mcp",
|
|
30
|
+
"model-context-protocol",
|
|
31
|
+
"llm",
|
|
32
|
+
"ai-tools",
|
|
33
|
+
"business-rules"
|
|
34
|
+
],
|
|
35
|
+
"author": {
|
|
36
|
+
"name": "Tomas Maritano",
|
|
37
|
+
"url": "https://github.com/tomymaritano"
|
|
38
|
+
},
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/tomymaritano/criterionx.git",
|
|
43
|
+
"directory": "packages/mcp"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/tomymaritano/criterionx/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/tomymaritano/criterionx#readme",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@criterionx/core": "workspace:*",
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"zod": "^3.22.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^20.0.0",
|
|
58
|
+
"tsup": "^8.0.0",
|
|
59
|
+
"typescript": "^5.3.0",
|
|
60
|
+
"vitest": "^4.0.16",
|
|
61
|
+
"zod": "^3.22.0"
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=18"
|
|
65
|
+
}
|
|
66
|
+
}
|