cks-mcp 0.1.1__tar.gz
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.
- cks_mcp-0.1.1/LICENSE +21 -0
- cks_mcp-0.1.1/PKG-INFO +125 -0
- cks_mcp-0.1.1/README.md +110 -0
- cks_mcp-0.1.1/pyproject.toml +27 -0
- cks_mcp-0.1.1/setup.cfg +4 -0
- cks_mcp-0.1.1/src/cks_mcp/__init__.py +0 -0
- cks_mcp-0.1.1/src/cks_mcp/server.py +212 -0
- cks_mcp-0.1.1/src/cks_mcp/tools/__init__.py +0 -0
- cks_mcp-0.1.1/src/cks_mcp/tools/compare.py +51 -0
- cks_mcp-0.1.1/src/cks_mcp/tools/derive.py +58 -0
- cks_mcp-0.1.1/src/cks_mcp/tools/evolve.py +92 -0
- cks_mcp-0.1.1/src/cks_mcp/tools/query.py +45 -0
- cks_mcp-0.1.1/src/cks_mcp/tools/validate.py +51 -0
- cks_mcp-0.1.1/src/cks_mcp.egg-info/PKG-INFO +125 -0
- cks_mcp-0.1.1/src/cks_mcp.egg-info/SOURCES.txt +23 -0
- cks_mcp-0.1.1/src/cks_mcp.egg-info/dependency_links.txt +1 -0
- cks_mcp-0.1.1/src/cks_mcp.egg-info/entry_points.txt +2 -0
- cks_mcp-0.1.1/src/cks_mcp.egg-info/requires.txt +5 -0
- cks_mcp-0.1.1/src/cks_mcp.egg-info/top_level.txt +1 -0
- cks_mcp-0.1.1/tests/test_compare.py +42 -0
- cks_mcp-0.1.1/tests/test_derive.py +59 -0
- cks_mcp-0.1.1/tests/test_evolve.py +49 -0
- cks_mcp-0.1.1/tests/test_query.py +31 -0
- cks_mcp-0.1.1/tests/test_server.py +109 -0
- cks_mcp-0.1.1/tests/test_validate.py +40 -0
cks_mcp-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Deus-corp
|
|
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.
|
cks_mcp-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cks-mcp
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: CKS MCP Server — Canonical Knowledge Structure for LLMs
|
|
5
|
+
Author: Vladyslav Hruznov
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: cks-core>=1.1.1
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# CKS MCP Server
|
|
17
|
+
|
|
18
|
+
> Model Context Protocol server for Canonical Knowledge Structure.
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+

|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
`cks-mcp` is an MCP (Model Context Protocol) server that provides LLMs
|
|
25
|
+
with structured, verifiable knowledge operations through the CKS
|
|
26
|
+
ecosystem. It exposes six tools—validate, query, compare, evolve,
|
|
27
|
+
derive, and construct—each backed by the deterministic, immutable
|
|
28
|
+
semantics of `cks-core`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Why cks-mcp?
|
|
33
|
+
|
|
34
|
+
LLMs generate plausible but unverified statements. `cks-mcp` gives them
|
|
35
|
+
a **canonical knowledge backbone**: every piece of information must be
|
|
36
|
+
explicitly structured, validated against formal constraints, and
|
|
37
|
+
traceable to its origin. This minimises hallucinations and makes AI‑
|
|
38
|
+
generated knowledge auditable.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
# Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install cks-mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The server requires `cks-core` (installed automatically as a dependency).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
# Quick Start
|
|
53
|
+
|
|
54
|
+
Launch the server:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cks-mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
An MCP client (Claude Desktop, any MCP-compatible LLM) can then connect
|
|
61
|
+
and call tools.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
# Available Tools
|
|
66
|
+
|
|
67
|
+
| Tool | Description |
|
|
68
|
+
|------|-------------|
|
|
69
|
+
| `validate_knowledge` | Validate a Knowledge Structure and return diagnostics. |
|
|
70
|
+
| `query_relations` | Find all relations for a given entity. |
|
|
71
|
+
| `compare_structures`| Check semantic equivalence of two structures. |
|
|
72
|
+
| `evolve_knowledge` | Apply Genesis/Decay operators to evolve a structure. |
|
|
73
|
+
| `derive_knowledge` | Derive a new Knowledge Object from existing premises. |
|
|
74
|
+
| `construct_knowledge`| Parse and construct a Knowledge Structure (coming soon). |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
# Usage Example
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"jsonrpc": "2.0",
|
|
83
|
+
"id": 1,
|
|
84
|
+
"method": "tools/call",
|
|
85
|
+
"params": {
|
|
86
|
+
"name": "validate_knowledge",
|
|
87
|
+
"arguments": {
|
|
88
|
+
"json_data": "{\"objects\":[{\"identity\":{\"id\":\"obj-1\",\"type\":\"Definition\",\"name\":\"Test\"},\"structure\":{}}]}"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Response:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"jsonrpc": "2.0",
|
|
99
|
+
"id": 1,
|
|
100
|
+
"result": "{\"valid\": true, \"error_count\": 0, \"warning_count\": 0, \"diagnostics\": []}"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
# Testing
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python -m pytest -v
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
20 tests, all passing.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
# Ecosystem
|
|
117
|
+
|
|
118
|
+
- **cks-core** — the canonical knowledge engine ([repo](https://github.com/Deus-corp/CKS))
|
|
119
|
+
- **CKS Specifications** — formal theory behind the system ([DOI](https://zenodo.org/records/21332624))
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
# License
|
|
124
|
+
|
|
125
|
+
MIT
|
cks_mcp-0.1.1/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# CKS MCP Server
|
|
2
|
+
|
|
3
|
+
> Model Context Protocol server for Canonical Knowledge Structure.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
`cks-mcp` is an MCP (Model Context Protocol) server that provides LLMs
|
|
10
|
+
with structured, verifiable knowledge operations through the CKS
|
|
11
|
+
ecosystem. It exposes six tools—validate, query, compare, evolve,
|
|
12
|
+
derive, and construct—each backed by the deterministic, immutable
|
|
13
|
+
semantics of `cks-core`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Why cks-mcp?
|
|
18
|
+
|
|
19
|
+
LLMs generate plausible but unverified statements. `cks-mcp` gives them
|
|
20
|
+
a **canonical knowledge backbone**: every piece of information must be
|
|
21
|
+
explicitly structured, validated against formal constraints, and
|
|
22
|
+
traceable to its origin. This minimises hallucinations and makes AI‑
|
|
23
|
+
generated knowledge auditable.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install cks-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The server requires `cks-core` (installed automatically as a dependency).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Quick Start
|
|
38
|
+
|
|
39
|
+
Launch the server:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cks-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
An MCP client (Claude Desktop, any MCP-compatible LLM) can then connect
|
|
46
|
+
and call tools.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
# Available Tools
|
|
51
|
+
|
|
52
|
+
| Tool | Description |
|
|
53
|
+
|------|-------------|
|
|
54
|
+
| `validate_knowledge` | Validate a Knowledge Structure and return diagnostics. |
|
|
55
|
+
| `query_relations` | Find all relations for a given entity. |
|
|
56
|
+
| `compare_structures`| Check semantic equivalence of two structures. |
|
|
57
|
+
| `evolve_knowledge` | Apply Genesis/Decay operators to evolve a structure. |
|
|
58
|
+
| `derive_knowledge` | Derive a new Knowledge Object from existing premises. |
|
|
59
|
+
| `construct_knowledge`| Parse and construct a Knowledge Structure (coming soon). |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
# Usage Example
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"jsonrpc": "2.0",
|
|
68
|
+
"id": 1,
|
|
69
|
+
"method": "tools/call",
|
|
70
|
+
"params": {
|
|
71
|
+
"name": "validate_knowledge",
|
|
72
|
+
"arguments": {
|
|
73
|
+
"json_data": "{\"objects\":[{\"identity\":{\"id\":\"obj-1\",\"type\":\"Definition\",\"name\":\"Test\"},\"structure\":{}}]}"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Response:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"jsonrpc": "2.0",
|
|
84
|
+
"id": 1,
|
|
85
|
+
"result": "{\"valid\": true, \"error_count\": 0, \"warning_count\": 0, \"diagnostics\": []}"
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
# Testing
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python -m pytest -v
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
20 tests, all passing.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
# Ecosystem
|
|
102
|
+
|
|
103
|
+
- **cks-core** — the canonical knowledge engine ([repo](https://github.com/Deus-corp/CKS))
|
|
104
|
+
- **CKS Specifications** — formal theory behind the system ([DOI](https://zenodo.org/records/21332624))
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
# License
|
|
109
|
+
|
|
110
|
+
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cks-mcp"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "CKS MCP Server — Canonical Knowledge Structure for LLMs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{name = "Vladyslav Hruznov"}]
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"cks-core>=1.1.1",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.0",
|
|
20
|
+
"pytest-asyncio>=0.24",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
cks-mcp = "cks_mcp.server:main"
|
cks_mcp-0.1.1/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CKS MCP Server — JSON-RPC over stdio.
|
|
3
|
+
|
|
4
|
+
Implements the Model Context Protocol (MCP) to expose CKS tools
|
|
5
|
+
to LLM clients.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Any, Callable, Dict, Optional
|
|
14
|
+
|
|
15
|
+
from .tools.validate import validate_knowledge
|
|
16
|
+
from .tools.query import query_relations
|
|
17
|
+
from .tools.compare import compare_structures
|
|
18
|
+
from .tools.evolve import evolve_knowledge
|
|
19
|
+
from .tools.derive import derive_knowledge
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Registry of available tools
|
|
23
|
+
TOOLS: Dict[str, Dict[str, Any]] = {
|
|
24
|
+
"validate_knowledge": {
|
|
25
|
+
"name": "validate_knowledge",
|
|
26
|
+
"description": "Validate a canonical knowledge structure and return diagnostics.",
|
|
27
|
+
"inputSchema": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"json_data": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "A JSON string representing a Knowledge Structure.",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
"required": ["json_data"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
"query_relations": {
|
|
39
|
+
"name": "query_relations",
|
|
40
|
+
"description": "Query all relations for a given entity.",
|
|
41
|
+
"inputSchema": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"json_data": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "A JSON string representing a Knowledge Structure.",
|
|
47
|
+
},
|
|
48
|
+
"entity_id": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "The canonical identity of the entity to query.",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
"required": ["json_data", "entity_id"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
"compare_structures": {
|
|
57
|
+
"name": "compare_structures",
|
|
58
|
+
"description": "Compare two knowledge structures for semantic equivalence.",
|
|
59
|
+
"inputSchema": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"properties": {
|
|
62
|
+
"json_data_a": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "A JSON string representing the first Knowledge Structure.",
|
|
65
|
+
},
|
|
66
|
+
"json_data_b": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "A JSON string representing the second Knowledge Structure.",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
"required": ["json_data_a", "json_data_b"],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
"evolve_knowledge": {
|
|
75
|
+
"name": "evolve_knowledge",
|
|
76
|
+
"description": "Apply a sequence of structural operators to a knowledge structure.",
|
|
77
|
+
"inputSchema": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"json_data": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "A JSON string representing a Knowledge Structure.",
|
|
83
|
+
},
|
|
84
|
+
"operations": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"description": "A JSON string with an array of operation objects.",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
"required": ["json_data", "operations"],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
"derive_knowledge": {
|
|
93
|
+
"name": "derive_knowledge",
|
|
94
|
+
"description": "Derive a new Knowledge Object from existing premises.",
|
|
95
|
+
"inputSchema": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"properties": {
|
|
98
|
+
"json_data": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "A JSON string representing a Knowledge Structure.",
|
|
101
|
+
},
|
|
102
|
+
"premises": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "A JSON array of canonical identities used as premises.",
|
|
105
|
+
},
|
|
106
|
+
"rule": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "The canonical inference rule identifier.",
|
|
109
|
+
},
|
|
110
|
+
"conclusion_id": {
|
|
111
|
+
"type": "string",
|
|
112
|
+
"description": "Canonical identity for the new Knowledge Object.",
|
|
113
|
+
},
|
|
114
|
+
"conclusion_type": {
|
|
115
|
+
"type": "string",
|
|
116
|
+
"description": "Canonical type for the new Knowledge Object.",
|
|
117
|
+
},
|
|
118
|
+
"conclusion_name": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"description": "Human-readable name for the new Knowledge Object.",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
"required": ["json_data", "premises", "rule", "conclusion_id", "conclusion_type", "conclusion_name"],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Mapping from tool name to callable
|
|
129
|
+
HANDLERS: Dict[str, Callable[..., str]] = {
|
|
130
|
+
"validate_knowledge": validate_knowledge,
|
|
131
|
+
"query_relations": query_relations,
|
|
132
|
+
"compare_structures": compare_structures,
|
|
133
|
+
"evolve_knowledge": evolve_knowledge,
|
|
134
|
+
"derive_knowledge": derive_knowledge,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def handle_request(request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
139
|
+
"""Handle a single JSON-RPC request and return a response."""
|
|
140
|
+
method = request.get("method")
|
|
141
|
+
params = request.get("params", {})
|
|
142
|
+
req_id = request.get("id")
|
|
143
|
+
|
|
144
|
+
if method == "tools/list":
|
|
145
|
+
return {
|
|
146
|
+
"jsonrpc": "2.0",
|
|
147
|
+
"id": req_id,
|
|
148
|
+
"result": list(TOOLS.values()),
|
|
149
|
+
}
|
|
150
|
+
elif method == "tools/call":
|
|
151
|
+
tool_name = params.get("name")
|
|
152
|
+
tool_args = params.get("arguments", {})
|
|
153
|
+
handler = HANDLERS.get(tool_name)
|
|
154
|
+
if handler is None:
|
|
155
|
+
return {
|
|
156
|
+
"jsonrpc": "2.0",
|
|
157
|
+
"id": req_id,
|
|
158
|
+
"error": {"code": -32601, "message": f"Tool '{tool_name}' not found"},
|
|
159
|
+
}
|
|
160
|
+
try:
|
|
161
|
+
result = handler(**tool_args)
|
|
162
|
+
return {
|
|
163
|
+
"jsonrpc": "2.0",
|
|
164
|
+
"id": req_id,
|
|
165
|
+
"result": result,
|
|
166
|
+
}
|
|
167
|
+
except Exception as exc:
|
|
168
|
+
return {
|
|
169
|
+
"jsonrpc": "2.0",
|
|
170
|
+
"id": req_id,
|
|
171
|
+
"error": {"code": -32000, "message": str(exc)},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
"jsonrpc": "2.0",
|
|
176
|
+
"id": req_id,
|
|
177
|
+
"error": {"code": -32601, "message": f"Method '{method}' not found"},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
async def main_loop() -> None:
|
|
182
|
+
"""Read JSON-RPC requests from stdin and write responses to stdout."""
|
|
183
|
+
reader = asyncio.StreamReader()
|
|
184
|
+
await asyncio.get_event_loop().connect_read_pipe(
|
|
185
|
+
lambda: asyncio.StreamReaderProtocol(reader),
|
|
186
|
+
sys.stdin,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
writer = asyncio.StreamWriter(sys.stdout.buffer, None, None, None)
|
|
190
|
+
|
|
191
|
+
while True:
|
|
192
|
+
line = await reader.readline()
|
|
193
|
+
if not line:
|
|
194
|
+
break
|
|
195
|
+
try:
|
|
196
|
+
request = json.loads(line.decode("utf-8"))
|
|
197
|
+
except json.JSONDecodeError:
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
response = await handle_request(request)
|
|
201
|
+
if response is not None:
|
|
202
|
+
writer.write(json.dumps(response).encode("utf-8") + b"\n")
|
|
203
|
+
await writer.drain()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def main() -> None:
|
|
207
|
+
"""Entry point for the MCP server."""
|
|
208
|
+
asyncio.run(main_loop())
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
if __name__ == "__main__":
|
|
212
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tool: compare_structures.
|
|
3
|
+
|
|
4
|
+
Compares two knowledge structures for semantic equivalence.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
from cks.serialization import parse as cks_parse, SerializationError
|
|
11
|
+
from cks.core import KnowledgeStructure
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def compare_structures(json_data_a: str, json_data_b: str) -> str:
|
|
15
|
+
"""
|
|
16
|
+
Compare two knowledge structures for semantic equivalence.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
json_data_a: A JSON string representing the first Knowledge Structure.
|
|
20
|
+
json_data_b: A JSON string representing the second Knowledge Structure.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
A JSON string with equivalence result and details.
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
data_a = json.loads(json_data_a) if isinstance(json_data_a, str) else json_data_a
|
|
27
|
+
structure_a = cks_parse(data_a)
|
|
28
|
+
except (json.JSONDecodeError, SerializationError) as exc:
|
|
29
|
+
return json.dumps({
|
|
30
|
+
"error": f"Failed to parse first structure: {exc}",
|
|
31
|
+
"equivalent": False,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
data_b = json.loads(json_data_b) if isinstance(json_data_b, str) else json_data_b
|
|
36
|
+
structure_b = cks_parse(data_b)
|
|
37
|
+
except (json.JSONDecodeError, SerializationError) as exc:
|
|
38
|
+
return json.dumps({
|
|
39
|
+
"error": f"Failed to parse second structure: {exc}",
|
|
40
|
+
"equivalent": False,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
is_equivalent = structure_a.structurally_equivalent(structure_b)
|
|
44
|
+
|
|
45
|
+
return json.dumps({
|
|
46
|
+
"equivalent": is_equivalent,
|
|
47
|
+
"objects_a": len(structure_a.objects),
|
|
48
|
+
"objects_b": len(structure_b.objects),
|
|
49
|
+
"relations_a": len(structure_a.relations()),
|
|
50
|
+
"relations_b": len(structure_b.relations()),
|
|
51
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tool: derive_knowledge.
|
|
3
|
+
|
|
4
|
+
Creates a new Knowledge Object derived from existing ones via a canonical derivation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
|
|
10
|
+
from cks.serialization import parse as cks_parse, SerializationError
|
|
11
|
+
from cks.core import KnowledgeObject, ObjectIdentity
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def derive_knowledge(json_data: str, premises: str, rule: str, conclusion_id: str, conclusion_type: str, conclusion_name: str) -> str:
|
|
15
|
+
"""
|
|
16
|
+
Derive a new Knowledge Object from existing premises.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
json_data: A JSON string representing a Knowledge Structure.
|
|
20
|
+
premises: A JSON array of canonical identities used as premises.
|
|
21
|
+
rule: The canonical inference rule identifier.
|
|
22
|
+
conclusion_id: Canonical identity for the new Knowledge Object.
|
|
23
|
+
conclusion_type: Canonical type for the new Knowledge Object.
|
|
24
|
+
conclusion_name: Human-readable name for the new Knowledge Object.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
A JSON string with the updated Knowledge Structure containing the derivation.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
data = json.loads(json_data) if isinstance(json_data, str) else json_data
|
|
31
|
+
structure = cks_parse(data)
|
|
32
|
+
except (json.JSONDecodeError, SerializationError) as exc:
|
|
33
|
+
return json.dumps({"error": f"Failed to parse input: {exc}"})
|
|
34
|
+
|
|
35
|
+
premise_ids = json.loads(premises) if isinstance(premises, str) else premises
|
|
36
|
+
for pid in premise_ids:
|
|
37
|
+
if structure.get(pid) is None:
|
|
38
|
+
return json.dumps({"error": f"Premise '{pid}' not found in structure"})
|
|
39
|
+
|
|
40
|
+
if structure.get(conclusion_id) is not None:
|
|
41
|
+
return json.dumps({"error": f"Conclusion '{conclusion_id}' already exists in structure"})
|
|
42
|
+
|
|
43
|
+
conclusion = KnowledgeObject(
|
|
44
|
+
identity=ObjectIdentity(id=conclusion_id, type=conclusion_type, name=conclusion_name),
|
|
45
|
+
structure={
|
|
46
|
+
"derived_from": premise_ids,
|
|
47
|
+
"rule": rule,
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Build a new structure with the conclusion added
|
|
52
|
+
objects = list(structure.objects)
|
|
53
|
+
objects.append(conclusion)
|
|
54
|
+
from cks.core import KnowledgeStructure
|
|
55
|
+
new_structure = KnowledgeStructure(objects)
|
|
56
|
+
|
|
57
|
+
from cks.serialization import serialize as cks_serialize
|
|
58
|
+
return cks_serialize(new_structure)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tool: evolve_knowledge.
|
|
3
|
+
|
|
4
|
+
Applies a sequence of structural operators to a knowledge structure.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
|
|
10
|
+
from cks.serialization import parse as cks_parse, SerializationError
|
|
11
|
+
from cks.evolution import compose, AddObject, AddRelation, RemoveObject, RemoveRelation
|
|
12
|
+
from cks.core import KnowledgeObject, CanonicalRelation, ObjectIdentity
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _parse_operations(ops_data: List[Dict[str, Any]]) -> List[Any]:
|
|
16
|
+
"""Parse a list of operation dictionaries into structural operators."""
|
|
17
|
+
operators = []
|
|
18
|
+
for i, op in enumerate(ops_data):
|
|
19
|
+
op_type = op.get("type")
|
|
20
|
+
if op_type is None:
|
|
21
|
+
raise ValueError(f"Operation #{i}: missing 'type' field")
|
|
22
|
+
if op_type == "add_object":
|
|
23
|
+
identity_data = op.get("identity")
|
|
24
|
+
if identity_data is None:
|
|
25
|
+
raise ValueError(f"Operation #{i}: missing 'identity' field")
|
|
26
|
+
identity = ObjectIdentity(**identity_data)
|
|
27
|
+
obj = KnowledgeObject(identity=identity, structure=op.get("structure", {}))
|
|
28
|
+
operators.append(AddObject(obj))
|
|
29
|
+
elif op_type == "add_relation":
|
|
30
|
+
identity_data = op.get("identity")
|
|
31
|
+
if identity_data is None:
|
|
32
|
+
raise ValueError(f"Operation #{i}: missing 'identity' field")
|
|
33
|
+
identity = ObjectIdentity(**identity_data)
|
|
34
|
+
participants = op.get("participants")
|
|
35
|
+
if participants is None:
|
|
36
|
+
raise ValueError(f"Operation #{i}: missing 'participants' field")
|
|
37
|
+
relation_type = op.get("relation_type")
|
|
38
|
+
if relation_type is None:
|
|
39
|
+
raise ValueError(f"Operation #{i}: missing 'relation_type' field")
|
|
40
|
+
relation = CanonicalRelation(
|
|
41
|
+
identity=identity,
|
|
42
|
+
participants=participants,
|
|
43
|
+
relation_type=relation_type,
|
|
44
|
+
structure=op.get("structure", {}),
|
|
45
|
+
)
|
|
46
|
+
operators.append(AddRelation(relation))
|
|
47
|
+
elif op_type == "remove_object":
|
|
48
|
+
object_id = op.get("object_id")
|
|
49
|
+
if object_id is None:
|
|
50
|
+
raise ValueError(f"Operation #{i}: missing 'object_id' field")
|
|
51
|
+
operators.append(RemoveObject(object_id))
|
|
52
|
+
elif op_type == "remove_relation":
|
|
53
|
+
relation_id = op.get("relation_id")
|
|
54
|
+
if relation_id is None:
|
|
55
|
+
raise ValueError(f"Operation #{i}: missing 'relation_id' field")
|
|
56
|
+
operators.append(RemoveRelation(relation_id))
|
|
57
|
+
else:
|
|
58
|
+
raise ValueError(f"Operation #{i}: unknown operation type '{op_type}'")
|
|
59
|
+
return operators
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def evolve_knowledge(json_data: str, operations: str) -> str:
|
|
63
|
+
"""
|
|
64
|
+
Apply a sequence of structural operators to a knowledge structure.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
json_data: A JSON string representing a Knowledge Structure.
|
|
68
|
+
operations: A JSON string with an array of operation objects.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
A JSON string with the evolved Knowledge Structure.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
data = json.loads(json_data) if isinstance(json_data, str) else json_data
|
|
75
|
+
structure = cks_parse(data)
|
|
76
|
+
except (json.JSONDecodeError, SerializationError) as exc:
|
|
77
|
+
return json.dumps({"error": f"Failed to parse input: {exc}"})
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
ops_data = json.loads(operations) if isinstance(operations, str) else operations
|
|
81
|
+
operators = _parse_operations(ops_data)
|
|
82
|
+
except (json.JSONDecodeError, ValueError) as exc:
|
|
83
|
+
return json.dumps({"error": f"Failed to parse operations: {exc}"})
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
evolved = compose(structure, operators)
|
|
87
|
+
except Exception as exc:
|
|
88
|
+
return json.dumps({"error": f"Evolution failed: {exc}"})
|
|
89
|
+
|
|
90
|
+
# Serialize back to JSON
|
|
91
|
+
from cks.serialization import serialize as cks_serialize
|
|
92
|
+
return cks_serialize(evolved)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tool: query_relations.
|
|
3
|
+
|
|
4
|
+
Returns all canonical relations for a given entity.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any, Dict, List
|
|
9
|
+
|
|
10
|
+
from cks.serialization import parse as cks_parse, SerializationError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def query_relations(json_data: str, entity_id: str) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Query all relations for a given entity in a knowledge structure.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
json_data: A JSON string representing a Knowledge Structure.
|
|
19
|
+
entity_id: The canonical identity of the entity to query.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A JSON string with the list of relations involving the entity.
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
data = json.loads(json_data) if isinstance(json_data, str) else json_data
|
|
26
|
+
structure = cks_parse(data)
|
|
27
|
+
except (json.JSONDecodeError, SerializationError) as exc:
|
|
28
|
+
return json.dumps({
|
|
29
|
+
"error": f"Failed to parse input: {exc}",
|
|
30
|
+
"relations": [],
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
relations: List[Dict[str, Any]] = []
|
|
34
|
+
for rel in structure.relations():
|
|
35
|
+
if entity_id in rel.participants:
|
|
36
|
+
relations.append({
|
|
37
|
+
"relation_id": rel.identity.id,
|
|
38
|
+
"relation_type": rel.relation_type,
|
|
39
|
+
"participants": list(rel.participants),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return json.dumps({
|
|
43
|
+
"entity_id": entity_id,
|
|
44
|
+
"relations": relations,
|
|
45
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tool: validate_knowledge.
|
|
3
|
+
|
|
4
|
+
Validates a canonical knowledge structure and returns diagnostics.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
from cks.serialization import parse as cks_parse, SerializationError
|
|
11
|
+
from cks.validator import validate as cks_validate
|
|
12
|
+
from cks.diagnostics import DiagnosticSeverity
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def validate_knowledge(json_data: str) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Validate a knowledge structure in canonical JSON format.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
json_data: A JSON string representing a Knowledge Structure.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
A JSON string with validation result and diagnostics.
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
data = json.loads(json_data) if isinstance(json_data, str) else json_data
|
|
27
|
+
structure = cks_parse(data)
|
|
28
|
+
except (json.JSONDecodeError, SerializationError) as exc:
|
|
29
|
+
return json.dumps({
|
|
30
|
+
"valid": False,
|
|
31
|
+
"error": f"Failed to parse input: {exc}",
|
|
32
|
+
"diagnostics": [],
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
result = cks_validate(structure)
|
|
36
|
+
|
|
37
|
+
diagnostics = []
|
|
38
|
+
for diag in result.diagnostics:
|
|
39
|
+
diagnostics.append({
|
|
40
|
+
"identity": diag.identity,
|
|
41
|
+
"severity": diag.severity.value,
|
|
42
|
+
"message": diag.message,
|
|
43
|
+
"location": diag.location,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return json.dumps({
|
|
47
|
+
"valid": result.is_valid,
|
|
48
|
+
"error_count": result.error_count,
|
|
49
|
+
"warning_count": result.warning_count,
|
|
50
|
+
"diagnostics": diagnostics,
|
|
51
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cks-mcp
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: CKS MCP Server — Canonical Knowledge Structure for LLMs
|
|
5
|
+
Author: Vladyslav Hruznov
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: cks-core>=1.1.1
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# CKS MCP Server
|
|
17
|
+
|
|
18
|
+
> Model Context Protocol server for Canonical Knowledge Structure.
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+

|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
`cks-mcp` is an MCP (Model Context Protocol) server that provides LLMs
|
|
25
|
+
with structured, verifiable knowledge operations through the CKS
|
|
26
|
+
ecosystem. It exposes six tools—validate, query, compare, evolve,
|
|
27
|
+
derive, and construct—each backed by the deterministic, immutable
|
|
28
|
+
semantics of `cks-core`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Why cks-mcp?
|
|
33
|
+
|
|
34
|
+
LLMs generate plausible but unverified statements. `cks-mcp` gives them
|
|
35
|
+
a **canonical knowledge backbone**: every piece of information must be
|
|
36
|
+
explicitly structured, validated against formal constraints, and
|
|
37
|
+
traceable to its origin. This minimises hallucinations and makes AI‑
|
|
38
|
+
generated knowledge auditable.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
# Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install cks-mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The server requires `cks-core` (installed automatically as a dependency).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
# Quick Start
|
|
53
|
+
|
|
54
|
+
Launch the server:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cks-mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
An MCP client (Claude Desktop, any MCP-compatible LLM) can then connect
|
|
61
|
+
and call tools.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
# Available Tools
|
|
66
|
+
|
|
67
|
+
| Tool | Description |
|
|
68
|
+
|------|-------------|
|
|
69
|
+
| `validate_knowledge` | Validate a Knowledge Structure and return diagnostics. |
|
|
70
|
+
| `query_relations` | Find all relations for a given entity. |
|
|
71
|
+
| `compare_structures`| Check semantic equivalence of two structures. |
|
|
72
|
+
| `evolve_knowledge` | Apply Genesis/Decay operators to evolve a structure. |
|
|
73
|
+
| `derive_knowledge` | Derive a new Knowledge Object from existing premises. |
|
|
74
|
+
| `construct_knowledge`| Parse and construct a Knowledge Structure (coming soon). |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
# Usage Example
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"jsonrpc": "2.0",
|
|
83
|
+
"id": 1,
|
|
84
|
+
"method": "tools/call",
|
|
85
|
+
"params": {
|
|
86
|
+
"name": "validate_knowledge",
|
|
87
|
+
"arguments": {
|
|
88
|
+
"json_data": "{\"objects\":[{\"identity\":{\"id\":\"obj-1\",\"type\":\"Definition\",\"name\":\"Test\"},\"structure\":{}}]}"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Response:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"jsonrpc": "2.0",
|
|
99
|
+
"id": 1,
|
|
100
|
+
"result": "{\"valid\": true, \"error_count\": 0, \"warning_count\": 0, \"diagnostics\": []}"
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
# Testing
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python -m pytest -v
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
20 tests, all passing.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
# Ecosystem
|
|
117
|
+
|
|
118
|
+
- **cks-core** — the canonical knowledge engine ([repo](https://github.com/Deus-corp/CKS))
|
|
119
|
+
- **CKS Specifications** — formal theory behind the system ([DOI](https://zenodo.org/records/21332624))
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
# License
|
|
124
|
+
|
|
125
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/cks_mcp/__init__.py
|
|
5
|
+
src/cks_mcp/server.py
|
|
6
|
+
src/cks_mcp.egg-info/PKG-INFO
|
|
7
|
+
src/cks_mcp.egg-info/SOURCES.txt
|
|
8
|
+
src/cks_mcp.egg-info/dependency_links.txt
|
|
9
|
+
src/cks_mcp.egg-info/entry_points.txt
|
|
10
|
+
src/cks_mcp.egg-info/requires.txt
|
|
11
|
+
src/cks_mcp.egg-info/top_level.txt
|
|
12
|
+
src/cks_mcp/tools/__init__.py
|
|
13
|
+
src/cks_mcp/tools/compare.py
|
|
14
|
+
src/cks_mcp/tools/derive.py
|
|
15
|
+
src/cks_mcp/tools/evolve.py
|
|
16
|
+
src/cks_mcp/tools/query.py
|
|
17
|
+
src/cks_mcp/tools/validate.py
|
|
18
|
+
tests/test_compare.py
|
|
19
|
+
tests/test_derive.py
|
|
20
|
+
tests/test_evolve.py
|
|
21
|
+
tests/test_query.py
|
|
22
|
+
tests/test_server.py
|
|
23
|
+
tests/test_validate.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cks_mcp
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Tests for compare_structures tool."""
|
|
2
|
+
import json
|
|
3
|
+
from cks_mcp.tools.compare import compare_structures
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_compare_equivalent():
|
|
7
|
+
"""Two identical structures should be equivalent."""
|
|
8
|
+
data_a = json.dumps({
|
|
9
|
+
"objects": [
|
|
10
|
+
{"identity": {"id": "obj-1", "type": "Definition", "name": "Test"}, "structure": {}},
|
|
11
|
+
]
|
|
12
|
+
})
|
|
13
|
+
data_b = json.dumps({
|
|
14
|
+
"objects": [
|
|
15
|
+
{"identity": {"id": "obj-1", "type": "Definition", "name": "Test"}, "structure": {}},
|
|
16
|
+
]
|
|
17
|
+
})
|
|
18
|
+
result = json.loads(compare_structures(data_a, data_b))
|
|
19
|
+
assert result["equivalent"] is True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_compare_different():
|
|
23
|
+
"""Structures with different objects should not be equivalent."""
|
|
24
|
+
data_a = json.dumps({
|
|
25
|
+
"objects": [
|
|
26
|
+
{"identity": {"id": "obj-1", "type": "Definition", "name": "Test"}, "structure": {}},
|
|
27
|
+
]
|
|
28
|
+
})
|
|
29
|
+
data_b = json.dumps({
|
|
30
|
+
"objects": [
|
|
31
|
+
{"identity": {"id": "obj-2", "type": "Definition", "name": "Other"}, "structure": {}},
|
|
32
|
+
]
|
|
33
|
+
})
|
|
34
|
+
result = json.loads(compare_structures(data_a, data_b))
|
|
35
|
+
assert result["equivalent"] is False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_compare_parse_error():
|
|
39
|
+
"""Malformed input should return an error."""
|
|
40
|
+
result = json.loads(compare_structures("not json", json.dumps({"objects": []})))
|
|
41
|
+
assert result["equivalent"] is False
|
|
42
|
+
assert "error" in result
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Tests for derive_knowledge tool."""
|
|
2
|
+
import json
|
|
3
|
+
from cks_mcp.tools.derive import derive_knowledge
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_derive_success():
|
|
7
|
+
"""Derive a new object from existing premises."""
|
|
8
|
+
data = json.dumps({
|
|
9
|
+
"objects": [
|
|
10
|
+
{"identity": {"id": "prem-1", "type": "Axiom", "name": "A"}, "structure": {}},
|
|
11
|
+
{"identity": {"id": "prem-2", "type": "Axiom", "name": "B"}, "structure": {}},
|
|
12
|
+
]
|
|
13
|
+
})
|
|
14
|
+
result = derive_knowledge(
|
|
15
|
+
json_data=data,
|
|
16
|
+
premises=json.dumps(["prem-1", "prem-2"]),
|
|
17
|
+
rule="modus_ponens",
|
|
18
|
+
conclusion_id="conc-1",
|
|
19
|
+
conclusion_type="Theorem",
|
|
20
|
+
conclusion_name="C",
|
|
21
|
+
)
|
|
22
|
+
assert "conc-1" in result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_derive_missing_premise():
|
|
26
|
+
"""Derivation with missing premise should return an error."""
|
|
27
|
+
data = json.dumps({
|
|
28
|
+
"objects": [
|
|
29
|
+
{"identity": {"id": "prem-1", "type": "Axiom", "name": "A"}, "structure": {}},
|
|
30
|
+
]
|
|
31
|
+
})
|
|
32
|
+
result = json.loads(derive_knowledge(
|
|
33
|
+
json_data=data,
|
|
34
|
+
premises=json.dumps(["prem-1", "prem-missing"]),
|
|
35
|
+
rule="modus_ponens",
|
|
36
|
+
conclusion_id="conc-1",
|
|
37
|
+
conclusion_type="Theorem",
|
|
38
|
+
conclusion_name="C",
|
|
39
|
+
))
|
|
40
|
+
assert "error" in result
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_derive_duplicate_id():
|
|
44
|
+
"""Derivation with duplicate conclusion ID should return an error."""
|
|
45
|
+
data = json.dumps({
|
|
46
|
+
"objects": [
|
|
47
|
+
{"identity": {"id": "prem-1", "type": "Axiom", "name": "A"}, "structure": {}},
|
|
48
|
+
{"identity": {"id": "conc-1", "type": "Theorem", "name": "Already exists"}, "structure": {}},
|
|
49
|
+
]
|
|
50
|
+
})
|
|
51
|
+
result = json.loads(derive_knowledge(
|
|
52
|
+
json_data=data,
|
|
53
|
+
premises=json.dumps(["prem-1"]),
|
|
54
|
+
rule="modus_ponens",
|
|
55
|
+
conclusion_id="conc-1",
|
|
56
|
+
conclusion_type="Theorem",
|
|
57
|
+
conclusion_name="C",
|
|
58
|
+
))
|
|
59
|
+
assert "error" in result
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Tests for evolve_knowledge tool."""
|
|
2
|
+
import json
|
|
3
|
+
from cks_mcp.tools.evolve import evolve_knowledge
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_evolve_add_object():
|
|
7
|
+
"""Add a new object via evolution."""
|
|
8
|
+
data = json.dumps({
|
|
9
|
+
"objects": [
|
|
10
|
+
{"identity": {"id": "obj-1", "type": "Definition", "name": "Test"}, "structure": {}},
|
|
11
|
+
]
|
|
12
|
+
})
|
|
13
|
+
ops = json.dumps([
|
|
14
|
+
{
|
|
15
|
+
"type": "add_object",
|
|
16
|
+
"identity": {"id": "obj-2", "type": "Definition", "name": "New"},
|
|
17
|
+
"structure": {},
|
|
18
|
+
}
|
|
19
|
+
])
|
|
20
|
+
result = evolve_knowledge(data, ops)
|
|
21
|
+
assert "obj-2" in result
|
|
22
|
+
assert "obj-1" in result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_evolve_remove_object():
|
|
26
|
+
"""Remove an object via evolution."""
|
|
27
|
+
data = json.dumps({
|
|
28
|
+
"objects": [
|
|
29
|
+
{"identity": {"id": "obj-1", "type": "Definition", "name": "Test"}, "structure": {}},
|
|
30
|
+
{"identity": {"id": "obj-2", "type": "Definition", "name": "Other"}, "structure": {}},
|
|
31
|
+
]
|
|
32
|
+
})
|
|
33
|
+
ops = json.dumps([
|
|
34
|
+
{"type": "remove_object", "object_id": "obj-2"}
|
|
35
|
+
])
|
|
36
|
+
result = evolve_knowledge(data, ops)
|
|
37
|
+
assert "obj-1" in result
|
|
38
|
+
assert "obj-2" not in result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_evolve_invalid_operations():
|
|
42
|
+
"""Invalid operations should return an error."""
|
|
43
|
+
data = json.dumps({
|
|
44
|
+
"objects": [
|
|
45
|
+
{"identity": {"id": "obj-1", "type": "Definition", "name": "Test"}, "structure": {}},
|
|
46
|
+
]
|
|
47
|
+
})
|
|
48
|
+
result = json.loads(evolve_knowledge(data, "invalid"))
|
|
49
|
+
assert "error" in result
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Tests for query_relations tool."""
|
|
2
|
+
import json
|
|
3
|
+
from cks_mcp.tools.query import query_relations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_query_relations():
|
|
7
|
+
"""Query an entity that has relations."""
|
|
8
|
+
data = json.dumps({
|
|
9
|
+
"objects": [
|
|
10
|
+
{"identity": {"id": "obj-1", "type": "Definition", "name": "Alice"}, "structure": {}},
|
|
11
|
+
{"identity": {"id": "obj-2", "type": "Definition", "name": "Bob"}, "structure": {}},
|
|
12
|
+
{
|
|
13
|
+
"identity": {"id": "rel-1", "type": "Relation", "name": "knows"},
|
|
14
|
+
"structure": {"participants": ["obj-1", "obj-2"], "relation_type": "knows"},
|
|
15
|
+
},
|
|
16
|
+
]
|
|
17
|
+
})
|
|
18
|
+
result = json.loads(query_relations(data, "obj-1"))
|
|
19
|
+
assert len(result["relations"]) == 1
|
|
20
|
+
assert result["relations"][0]["relation_type"] == "knows"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_query_no_relations():
|
|
24
|
+
"""Query an entity with no relations."""
|
|
25
|
+
data = json.dumps({
|
|
26
|
+
"objects": [
|
|
27
|
+
{"identity": {"id": "obj-1", "type": "Definition", "name": "Alice"}, "structure": {}},
|
|
28
|
+
]
|
|
29
|
+
})
|
|
30
|
+
result = json.loads(query_relations(data, "obj-1"))
|
|
31
|
+
assert len(result["relations"]) == 0
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Tests for MCP server."""
|
|
2
|
+
import json
|
|
3
|
+
import pytest
|
|
4
|
+
from cks_mcp.server import handle_request
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_tools_list():
|
|
9
|
+
"""tools/list should return available tools."""
|
|
10
|
+
request = {"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}
|
|
11
|
+
response = await handle_request(request)
|
|
12
|
+
assert response["jsonrpc"] == "2.0"
|
|
13
|
+
assert response["id"] == 1
|
|
14
|
+
tools = response["result"]
|
|
15
|
+
assert len(tools) >= 1
|
|
16
|
+
assert any(t["name"] == "validate_knowledge" for t in tools)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.asyncio
|
|
20
|
+
async def test_tools_call_validate():
|
|
21
|
+
"""tools/call for validate_knowledge should succeed with valid input."""
|
|
22
|
+
request = {
|
|
23
|
+
"jsonrpc": "2.0",
|
|
24
|
+
"id": 2,
|
|
25
|
+
"method": "tools/call",
|
|
26
|
+
"params": {
|
|
27
|
+
"name": "validate_knowledge",
|
|
28
|
+
"arguments": {
|
|
29
|
+
"json_data": json.dumps({
|
|
30
|
+
"objects": [
|
|
31
|
+
{
|
|
32
|
+
"identity": {"id": "obj-1", "type": "Definition", "name": "Test"},
|
|
33
|
+
"structure": {},
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
})
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
response = await handle_request(request)
|
|
41
|
+
assert response["jsonrpc"] == "2.0"
|
|
42
|
+
assert response["id"] == 2
|
|
43
|
+
result = json.loads(response["result"])
|
|
44
|
+
assert result["valid"] is True
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.asyncio
|
|
48
|
+
async def test_tools_call_validate_invalid():
|
|
49
|
+
"""tools/call for validate_knowledge should return error for invalid input."""
|
|
50
|
+
request = {
|
|
51
|
+
"jsonrpc": "2.0",
|
|
52
|
+
"id": 3,
|
|
53
|
+
"method": "tools/call",
|
|
54
|
+
"params": {
|
|
55
|
+
"name": "validate_knowledge",
|
|
56
|
+
"arguments": {
|
|
57
|
+
"json_data": json.dumps({
|
|
58
|
+
"objects": [
|
|
59
|
+
{"identity": {"id": "dup", "type": "X", "name": "x"}, "structure": {}},
|
|
60
|
+
{"identity": {"id": "dup", "type": "Y", "name": "y"}, "structure": {}},
|
|
61
|
+
]
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
response = await handle_request(request)
|
|
67
|
+
assert response["jsonrpc"] == "2.0"
|
|
68
|
+
assert response["id"] == 3
|
|
69
|
+
result = json.loads(response["result"])
|
|
70
|
+
assert result["valid"] is False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@pytest.mark.asyncio
|
|
74
|
+
async def test_tools_call_unknown_tool():
|
|
75
|
+
"""Calling an unknown tool should return an error."""
|
|
76
|
+
request = {
|
|
77
|
+
"jsonrpc": "2.0",
|
|
78
|
+
"id": 4,
|
|
79
|
+
"method": "tools/call",
|
|
80
|
+
"params": {
|
|
81
|
+
"name": "nonexistent",
|
|
82
|
+
"arguments": {},
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
response = await handle_request(request)
|
|
86
|
+
assert response["jsonrpc"] == "2.0"
|
|
87
|
+
assert response["id"] == 4
|
|
88
|
+
assert "error" in response
|
|
89
|
+
assert response["error"]["code"] == -32601
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.asyncio
|
|
93
|
+
async def test_unknown_method():
|
|
94
|
+
"""Calling an unknown method should return an error."""
|
|
95
|
+
request = {"jsonrpc": "2.0", "id": 5, "method": "unknown/method", "params": {}}
|
|
96
|
+
response = await handle_request(request)
|
|
97
|
+
assert response["jsonrpc"] == "2.0"
|
|
98
|
+
assert response["id"] == 5
|
|
99
|
+
assert "error" in response
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.mark.asyncio
|
|
103
|
+
async def test_missing_method():
|
|
104
|
+
"""Request with missing method should return an error."""
|
|
105
|
+
request = {"jsonrpc": "2.0", "id": 6}
|
|
106
|
+
response = await handle_request(request)
|
|
107
|
+
assert response["jsonrpc"] == "2.0"
|
|
108
|
+
assert response["id"] == 6
|
|
109
|
+
assert "error" in response
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Tests for validate_knowledge tool."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from cks_mcp.tools.validate import validate_knowledge
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_validate_valid_structure():
|
|
8
|
+
"""A valid minimal structure should pass validation."""
|
|
9
|
+
valid_json = json.dumps({
|
|
10
|
+
"objects": [
|
|
11
|
+
{
|
|
12
|
+
"identity": {"id": "obj-1", "type": "Definition", "name": "Test"},
|
|
13
|
+
"structure": {},
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
})
|
|
17
|
+
result = json.loads(validate_knowledge(valid_json))
|
|
18
|
+
assert result["valid"] is True
|
|
19
|
+
assert result["error_count"] == 0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_validate_invalid_structure():
|
|
23
|
+
"""An invalid structure (duplicate IDs) should fail."""
|
|
24
|
+
invalid_json = json.dumps({
|
|
25
|
+
"objects": [
|
|
26
|
+
{"identity": {"id": "dup", "type": "Definition", "name": "First"}, "structure": {}},
|
|
27
|
+
{"identity": {"id": "dup", "type": "Definition", "name": "Second"}, "structure": {}},
|
|
28
|
+
]
|
|
29
|
+
})
|
|
30
|
+
result = json.loads(validate_knowledge(invalid_json))
|
|
31
|
+
assert result["valid"] is False
|
|
32
|
+
# Should be a parse error (duplicate identity detected by parser)
|
|
33
|
+
assert "Duplicate" in result.get("error", "")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_validate_malformed_json():
|
|
37
|
+
"""Malformed JSON should return an error."""
|
|
38
|
+
result = json.loads(validate_knowledge("not json"))
|
|
39
|
+
assert result["valid"] is False
|
|
40
|
+
assert "error" in result
|