@totemsdk/mcp-server 0.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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +220 -0
- package/dist/indexer.d.ts +3 -0
- package/dist/indexer.js +221 -0
- package/dist/resources.d.ts +7 -0
- package/dist/resources.js +135 -0
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +416 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +2 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Totem SDK Contributors
|
|
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,69 @@
|
|
|
1
|
+
# @totemsdk/mcp-server
|
|
2
|
+
|
|
3
|
+
Model Context Protocol server exposing the full Totem SDK package set — 53 packages, 13k+ exports, cross-package dependency graphs, and scaffolding tools.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### Claude Desktop
|
|
8
|
+
|
|
9
|
+
Add to `claude_desktop_config.json`:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"totemsdk": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["-y", "@totemsdk/mcp-server"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Any MCP client
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx -y @totemsdk/mcp-server
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The server runs on stdio and communicates via JSON-RPC.
|
|
29
|
+
|
|
30
|
+
## Resources
|
|
31
|
+
|
|
32
|
+
| URI | Description |
|
|
33
|
+
|-----|-------------|
|
|
34
|
+
| `totemsdk://packages` | All packages with metadata |
|
|
35
|
+
| `totemsdk://packages/{name}` | Package metadata (deps, exports, domain) |
|
|
36
|
+
| `totemsdk://packages/{name}/exports` | Exported symbols |
|
|
37
|
+
| `totemsdk://packages/{name}/dependencies` | Dependency lists |
|
|
38
|
+
| `totemsdk://packages/by-domain/{layer}` | Packages in a domain |
|
|
39
|
+
| `totemsdk://conventions` | Coding conventions |
|
|
40
|
+
| `totemsdk://domain-map` | Packages grouped by domain |
|
|
41
|
+
| `totemsdk://symbol/{name}` | Symbol locations across packages |
|
|
42
|
+
|
|
43
|
+
## Tools
|
|
44
|
+
|
|
45
|
+
| Tool | Description |
|
|
46
|
+
|------|-------------|
|
|
47
|
+
| `search-symbol` | Find which packages export a symbol |
|
|
48
|
+
| `find-type` | Locate type definitions by name pattern |
|
|
49
|
+
| `dependency-graph` | Get outbound deps or inbound dependents |
|
|
50
|
+
| `validate-import` | Check if a cross-package import is valid |
|
|
51
|
+
| `scaffold-adapter` | Generate edge protocol adapter boilerplate |
|
|
52
|
+
| `scaffold-package` | Generate new package boilerplate |
|
|
53
|
+
| `package-stats` | Export counts, Rust/Go, tests, deps |
|
|
54
|
+
| `list-exports` | List exports filtered by kind and name |
|
|
55
|
+
|
|
56
|
+
## Prompts
|
|
57
|
+
|
|
58
|
+
| Prompt | Description |
|
|
59
|
+
|--------|-------------|
|
|
60
|
+
| `analyze-cross-package` | Compare two packages' relationship |
|
|
61
|
+
| `new-edge-adapter` | Walk through creating a protocol adapter |
|
|
62
|
+
|
|
63
|
+
## How it works
|
|
64
|
+
|
|
65
|
+
At startup, the server scans all `@totemsdk/*` packages in the monorepo, parses `package.json` and `src/index.ts` to build an in-memory index of 53 packages and their exports. No runtime dependency on any `@totemsdk/*` package.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
4
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
6
|
+
const indexer_js_1 = require("./indexer.js");
|
|
7
|
+
const resources_js_1 = require("./resources.js");
|
|
8
|
+
const tools_js_1 = require("./tools.js");
|
|
9
|
+
const index = (0, indexer_js_1.buildIndex)();
|
|
10
|
+
const server = new index_js_1.Server({ name: '@totemsdk/mcp-server', version: '0.1.0' }, { capabilities: { resources: {}, tools: {}, prompts: {} } });
|
|
11
|
+
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => ({
|
|
12
|
+
resources: (0, resources_js_1.listResources)(index),
|
|
13
|
+
}));
|
|
14
|
+
server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
|
|
15
|
+
const { uri } = request.params;
|
|
16
|
+
const text = (0, resources_js_1.handleResourceRead)(uri, index);
|
|
17
|
+
if (text === null) {
|
|
18
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
19
|
+
}
|
|
20
|
+
return { contents: [{ uri, mimeType: 'text/plain', text }] };
|
|
21
|
+
});
|
|
22
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
23
|
+
tools: [
|
|
24
|
+
{
|
|
25
|
+
name: 'search-symbol',
|
|
26
|
+
description: 'Search for a symbol (function, type, class) across all packages',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: { query: { type: 'string', description: 'Partial symbol name to search' } },
|
|
30
|
+
required: ['query'],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'find-type',
|
|
35
|
+
description: 'Find type definitions (interfaces, classes, type aliases) matching a pattern',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: { pattern: { type: 'string', description: 'Type name pattern to search' } },
|
|
39
|
+
required: ['pattern'],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'dependency-graph',
|
|
44
|
+
description: 'Get dependency graph for a package — inbound dependents or outbound dependencies',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
package: { type: 'string', description: 'Package name (e.g. @totemsdk/edge-opcua)' },
|
|
49
|
+
direction: { type: 'string', enum: ['in', 'out', 'all'], description: 'Dependency direction' },
|
|
50
|
+
},
|
|
51
|
+
required: ['package'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'validate-import',
|
|
56
|
+
description: 'Check whether a cross-package import is valid',
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
from: { type: 'string', description: 'Source package name' },
|
|
61
|
+
to: { type: 'string', description: 'Target package name' },
|
|
62
|
+
symbol: { type: 'string', description: 'Optional: specific symbol to check' },
|
|
63
|
+
},
|
|
64
|
+
required: ['from', 'to'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'scaffold-adapter',
|
|
69
|
+
description: 'Generate boilerplate for a new edge protocol adapter',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
name: { type: 'string', description: 'Package name suffix' },
|
|
74
|
+
protocol: { type: 'string', description: 'Protocol name (PascalCase)' },
|
|
75
|
+
commands: { type: 'array', items: { type: 'string' }, description: 'Transport port methods' },
|
|
76
|
+
},
|
|
77
|
+
required: ['name', 'protocol'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'scaffold-package',
|
|
82
|
+
description: 'Generate boilerplate for a new @totemsdk package',
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
name: { type: 'string', description: 'Package name (without @totemsdk/ prefix)' },
|
|
87
|
+
deps: { type: 'array', items: { type: 'string' }, description: 'Dependency package names' },
|
|
88
|
+
},
|
|
89
|
+
required: ['name'],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'package-stats',
|
|
94
|
+
description: 'Get statistics about a package — export counts, Rust/Go, tests, deps',
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: { name: { type: 'string', description: 'Package name' } },
|
|
98
|
+
required: ['name'],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'list-exports',
|
|
103
|
+
description: 'List exports of a package',
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
package: { type: 'string', description: 'Package name' },
|
|
108
|
+
kind: { type: 'string', enum: ['function', 'type', 'interface', 'class', 'const', ''], description: 'Filter by export kind' },
|
|
109
|
+
filter: { type: 'string', description: 'Filter by name substring' },
|
|
110
|
+
},
|
|
111
|
+
required: ['package'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
}));
|
|
116
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
117
|
+
const { name, arguments: args } = request.params;
|
|
118
|
+
return (0, tools_js_1.handleToolCall)(name, args || {}, index);
|
|
119
|
+
});
|
|
120
|
+
server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => ({
|
|
121
|
+
prompts: [
|
|
122
|
+
{
|
|
123
|
+
name: 'analyze-cross-package',
|
|
124
|
+
description: 'Analyze relationships and dependencies between two packages',
|
|
125
|
+
arguments: [
|
|
126
|
+
{ name: 'from', description: 'First package name', required: true },
|
|
127
|
+
{ name: 'to', description: 'Second package name', required: true },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'new-edge-adapter',
|
|
132
|
+
description: 'Walk through creating a new edge protocol adapter step by step',
|
|
133
|
+
arguments: [
|
|
134
|
+
{ name: 'protocol', description: 'Protocol name (e.g. MQTT, OPC-UA)', required: true },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}));
|
|
139
|
+
server.setRequestHandler(types_js_1.GetPromptRequestSchema, async (request) => {
|
|
140
|
+
const { name, arguments: args } = request.params;
|
|
141
|
+
if (name === 'analyze-cross-package') {
|
|
142
|
+
const from = args?.from;
|
|
143
|
+
const to = args?.to;
|
|
144
|
+
const fromPkg = from ? index.packages[from] : null;
|
|
145
|
+
const toPkg = to ? index.packages[to] : null;
|
|
146
|
+
if (!fromPkg || !toPkg) {
|
|
147
|
+
throw new Error(`Packages not found: ${!fromPkg ? from : ''} ${!toPkg ? to : ''}`);
|
|
148
|
+
}
|
|
149
|
+
const dependsOn = fromPkg.dependencies.includes(to);
|
|
150
|
+
const dependedBy = toPkg.dependencies.includes(from);
|
|
151
|
+
const fromExports = Object.entries(fromPkg.exports).flatMap(([k, v]) => v.map((s) => `${s} (${k})`));
|
|
152
|
+
const toExports = Object.entries(toPkg.exports).flatMap(([k, v]) => v.map((s) => `${s} (${k})`));
|
|
153
|
+
const lines = [
|
|
154
|
+
`Analyze the relationship between **${from}** and **${to}**:`,
|
|
155
|
+
'',
|
|
156
|
+
`| | ${from} | ${to} |`,
|
|
157
|
+
`|---|---|---|`,
|
|
158
|
+
`| Version | ${fromPkg.version} | ${toPkg.version} |`,
|
|
159
|
+
`| Domain | ${fromPkg.domain} | ${toPkg.domain} |`,
|
|
160
|
+
`| Has Rust | ${fromPkg.hasRust} | ${toPkg.hasRust} |`,
|
|
161
|
+
`| Has Go | ${fromPkg.hasGo} | ${toPkg.hasGo} |`,
|
|
162
|
+
`| Tests | ${fromPkg.hasTests} | ${toPkg.hasTests} |`,
|
|
163
|
+
`| Exports | ${fromExports.length} | ${toExports.length} |`,
|
|
164
|
+
'',
|
|
165
|
+
`**Dependency direction:** ${from} \u2192 ${to}: ${dependsOn ? 'Yes' : 'No'}`,
|
|
166
|
+
`${to} \u2192 ${from}: ${dependedBy ? 'Yes' : 'No'}`,
|
|
167
|
+
'',
|
|
168
|
+
dependsOn ? `**${from}** depends on **${to}**.` : '',
|
|
169
|
+
dependedBy ? `**${to}** depends on **${from}**.` : '',
|
|
170
|
+
!dependsOn && !dependedBy ? 'These packages have no direct dependency relationship.' : '',
|
|
171
|
+
'',
|
|
172
|
+
`**${from}** exports: ${fromExports.join(', ')}`,
|
|
173
|
+
'',
|
|
174
|
+
`**${to}** exports: ${toExports.join(', ')}`,
|
|
175
|
+
];
|
|
176
|
+
return {
|
|
177
|
+
messages: [{ role: 'user', content: { type: 'text', text: lines.filter(Boolean).join('\n') } }],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (name === 'new-edge-adapter') {
|
|
181
|
+
const protocol = args?.protocol || 'UnknownProtocol';
|
|
182
|
+
const existingAdapters = Object.values(index.packages).filter(p => p.domain === 'edge/protocols' && p.dir.startsWith('edge-'));
|
|
183
|
+
const lines = [
|
|
184
|
+
`You are scaffolding a new **${protocol}** edge protocol adapter for Totem SDK.`,
|
|
185
|
+
'',
|
|
186
|
+
`**Reference:** ${existingAdapters.length} existing protocol adapters:`,
|
|
187
|
+
...existingAdapters.map(p => ` - **${p.name}** — ${p.description}`),
|
|
188
|
+
'',
|
|
189
|
+
'**Required pattern for all edge adapters:**',
|
|
190
|
+
`1. Zero runtime protocol dependencies — inject via \`${protocol}TransportPort\``,
|
|
191
|
+
`2. Export \`create${protocol}Gateway\` and \`create${protocol}SensorBridge\` factory functions`,
|
|
192
|
+
`3. Export \`${protocol}TransportPort\` interface for users to implement`,
|
|
193
|
+
`4. Export gateway, sensor bridge, and binding config types`,
|
|
194
|
+
`5. Depend only on \`@totemsdk/edge\``,
|
|
195
|
+
'',
|
|
196
|
+
'**Files to create:**',
|
|
197
|
+
`- \`src/transport.ts\` — \`${protocol}TransportPort\` interface`,
|
|
198
|
+
`- \`src/gateway.ts\` — gateway factory with config type`,
|
|
199
|
+
`- \`src/sensor-bridge.ts\` — sensor bridge factory with binding config`,
|
|
200
|
+
`- \`src/index.ts\` — barrel exports`,
|
|
201
|
+
`- \`package.json\` — \`@totemsdk/edge-${protocol.toLowerCase()}\` with \`@totemsdk/edge\` dep`,
|
|
202
|
+
'- `tsconfig.json` — standard Totem SDK config',
|
|
203
|
+
'',
|
|
204
|
+
'Use the `scaffold-adapter` tool to generate the boilerplate, then fill in the protocol-specific logic.',
|
|
205
|
+
];
|
|
206
|
+
return {
|
|
207
|
+
messages: [{ role: 'user', content: { type: 'text', text: lines.join('\n') } }],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
throw new Error(`Prompt not found: ${name}`);
|
|
211
|
+
});
|
|
212
|
+
async function main() {
|
|
213
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
214
|
+
await server.connect(transport);
|
|
215
|
+
console.error('@totemsdk/mcp-server running on stdio');
|
|
216
|
+
}
|
|
217
|
+
main().catch((err) => {
|
|
218
|
+
console.error('Fatal error:', err);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
});
|
package/dist/indexer.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildIndex = buildIndex;
|
|
37
|
+
exports.readSourceFile = readSourceFile;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..', '..', '..');
|
|
41
|
+
const PKG_DIR = path.join(REPO_ROOT, 'packages', 'totem-sdk', 'packages');
|
|
42
|
+
const TOP_PKG_DIR = path.join(REPO_ROOT, 'packages');
|
|
43
|
+
const DOMAIN_GROUPS = [
|
|
44
|
+
[/^core-wasm$|^core$|^txpow$|^kissvm$/, 'core/crypto'],
|
|
45
|
+
[/^identity$|^root-identity$|^authority$|^proof$|^proof-integritas$|^proofgraph$|^manifest$/, 'identity/authority'],
|
|
46
|
+
[/^governance$|^recursive-mast$/, 'governance'],
|
|
47
|
+
[/^edge$|^edge-adapters$|^industrial-action$|^agent-policy$/, 'edge/runtime'],
|
|
48
|
+
[/^edge-(bacnet|ble|can|coap|grpc|lorawan|matter|modbus|mqtt|opcua|ros2)$/, 'edge/protocols'],
|
|
49
|
+
[/^omnia(-factory|-router|-splice|-vtxo)?$/, 'blockchain/omnia'],
|
|
50
|
+
[/^statechain$|^tx-builder$|^chain-provider$|^wots-lease$|^liquidity-bond$|^provider-bond$/, 'blockchain/infra'],
|
|
51
|
+
[/^lookup-(client|node|protocol)$/, 'lookup/p2p'],
|
|
52
|
+
[/^stream-transport$|^pubsub-transport$|^pureminima-rpc$|^server$|^se-server$|^realtime$|^connect$|^wallet-adapter$|^pear$|^observability$/, 'utilities'],
|
|
53
|
+
[/^sdk-tests$/, 'testing'],
|
|
54
|
+
];
|
|
55
|
+
function classifyDomain(dirName) {
|
|
56
|
+
for (const [pattern, domain] of DOMAIN_GROUPS) {
|
|
57
|
+
if (pattern.test(dirName))
|
|
58
|
+
return domain;
|
|
59
|
+
}
|
|
60
|
+
return 'other';
|
|
61
|
+
}
|
|
62
|
+
function findPackageDirs() {
|
|
63
|
+
const dirs = [];
|
|
64
|
+
for (const base of [PKG_DIR, TOP_PKG_DIR]) {
|
|
65
|
+
if (!fs.existsSync(base))
|
|
66
|
+
continue;
|
|
67
|
+
for (const entry of fs.readdirSync(base, { withFileTypes: true })) {
|
|
68
|
+
if (!entry.isDirectory())
|
|
69
|
+
continue;
|
|
70
|
+
const pj = path.join(base, entry.name, 'package.json');
|
|
71
|
+
if (fs.existsSync(pj))
|
|
72
|
+
dirs.push(path.join(base, entry.name));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return dirs.sort();
|
|
76
|
+
}
|
|
77
|
+
function readPackageJson(dir) {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function hasSubdir(dir, name) {
|
|
86
|
+
const d = path.join(dir, name);
|
|
87
|
+
return fs.existsSync(d) && fs.statSync(d).isDirectory();
|
|
88
|
+
}
|
|
89
|
+
function hasTestDir(dir) {
|
|
90
|
+
const srcTest = path.join(dir, 'src', '__tests__');
|
|
91
|
+
const srcTestAlt = path.join(dir, 'src', 'test');
|
|
92
|
+
const testDir = path.join(dir, 'test');
|
|
93
|
+
const testsDir = path.join(dir, 'tests');
|
|
94
|
+
return [srcTest, srcTestAlt, testDir, testsDir].some(d => fs.existsSync(d));
|
|
95
|
+
}
|
|
96
|
+
function parseExports(dir, pkgName) {
|
|
97
|
+
const indexFiles = [
|
|
98
|
+
path.join(dir, 'src', 'index.ts'),
|
|
99
|
+
path.join(dir, 'src', 'index.js'),
|
|
100
|
+
];
|
|
101
|
+
let content = '';
|
|
102
|
+
for (const f of indexFiles) {
|
|
103
|
+
if (fs.existsSync(f)) {
|
|
104
|
+
content = fs.readFileSync(f, 'utf-8');
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const exports = { functions: [], types: [], classes: [], interfaces: [], consts: [] };
|
|
109
|
+
if (!content)
|
|
110
|
+
return exports;
|
|
111
|
+
const lines = content.split('\n');
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
const trimmed = line.trim();
|
|
114
|
+
const fnMatch = trimmed.match(/^export (async )?function (\w+)/);
|
|
115
|
+
if (fnMatch) {
|
|
116
|
+
exports.functions.push(fnMatch[2]);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const typeMatch = trimmed.match(/^export type \{ ([^}]+) \}/);
|
|
120
|
+
if (typeMatch) {
|
|
121
|
+
typeMatch[1].split(',').map(s => s.trim()).filter(Boolean).forEach(s => {
|
|
122
|
+
const name = s.split(/\s+as\s+/).pop()?.trim() || s;
|
|
123
|
+
if (name)
|
|
124
|
+
exports.types.push(name);
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const clsMatch = trimmed.match(/^export class (\w+)/);
|
|
129
|
+
if (clsMatch) {
|
|
130
|
+
exports.classes.push(clsMatch[1]);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const ifaceMatch = trimmed.match(/^export interface (\w+)/);
|
|
134
|
+
if (ifaceMatch) {
|
|
135
|
+
exports.interfaces.push(ifaceMatch[1]);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const constMatch = trimmed.match(/^export (const|let|var) (\w+)/);
|
|
139
|
+
if (constMatch) {
|
|
140
|
+
exports.consts.push(constMatch[2]);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const namedExport = trimmed.match(/^export \{ ([^}]+) \}/);
|
|
144
|
+
if (namedExport) {
|
|
145
|
+
for (const part of namedExport[1].split(',')) {
|
|
146
|
+
const name = part.trim().split(/\s+as\s+/).pop()?.trim() || '';
|
|
147
|
+
if (name && !name.startsWith('type ') && name !== 'type') {
|
|
148
|
+
if (name.endsWith('}'))
|
|
149
|
+
continue;
|
|
150
|
+
if (/^[A-Z]/.test(name))
|
|
151
|
+
exports.types.push(name);
|
|
152
|
+
else
|
|
153
|
+
exports.functions.push(name);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return exports;
|
|
159
|
+
}
|
|
160
|
+
function buildIndex() {
|
|
161
|
+
const packages = {};
|
|
162
|
+
const symbolIndex = {};
|
|
163
|
+
const domainMap = {};
|
|
164
|
+
for (const dir of findPackageDirs()) {
|
|
165
|
+
const pkg = readPackageJson(dir);
|
|
166
|
+
if (!pkg || !pkg.name)
|
|
167
|
+
continue;
|
|
168
|
+
const dirName = path.basename(dir);
|
|
169
|
+
const deps = Object.keys(pkg.dependencies || {});
|
|
170
|
+
const devDeps = Object.keys(pkg.devDependencies || {});
|
|
171
|
+
const hasRust = hasSubdir(dir, 'rust') || hasSubdir(dir, 'rust-toolchain');
|
|
172
|
+
const hasGo = hasSubdir(dir, 'go');
|
|
173
|
+
const hasTests = hasTestDir(dir);
|
|
174
|
+
const exports = parseExports(dir, pkg.name);
|
|
175
|
+
const domain = classifyDomain(dirName);
|
|
176
|
+
const idx = {
|
|
177
|
+
name: pkg.name,
|
|
178
|
+
dir: dirName,
|
|
179
|
+
version: pkg.version || '0.0.0',
|
|
180
|
+
description: pkg.description || '',
|
|
181
|
+
dependencies: deps,
|
|
182
|
+
devDependencies: devDeps,
|
|
183
|
+
hasRust,
|
|
184
|
+
hasGo,
|
|
185
|
+
hasTests,
|
|
186
|
+
exports,
|
|
187
|
+
domain,
|
|
188
|
+
};
|
|
189
|
+
packages[pkg.name] = idx;
|
|
190
|
+
if (!domainMap[domain])
|
|
191
|
+
domainMap[domain] = [];
|
|
192
|
+
domainMap[domain].push(pkg.name);
|
|
193
|
+
const allExports = [];
|
|
194
|
+
for (const n of exports.functions)
|
|
195
|
+
allExports.push([n, 'function']);
|
|
196
|
+
for (const n of exports.types)
|
|
197
|
+
allExports.push([n, 'type']);
|
|
198
|
+
for (const n of exports.classes)
|
|
199
|
+
allExports.push([n, 'class']);
|
|
200
|
+
for (const n of exports.interfaces)
|
|
201
|
+
allExports.push([n, 'interface']);
|
|
202
|
+
for (const n of exports.consts)
|
|
203
|
+
allExports.push([n, 'const']);
|
|
204
|
+
for (const [name, kind] of allExports) {
|
|
205
|
+
if (!symbolIndex[name])
|
|
206
|
+
symbolIndex[name] = [];
|
|
207
|
+
symbolIndex[name].push({ package: pkg.name, kind });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { generatedAt: Date.now(), packages, symbolIndex, domainMap };
|
|
211
|
+
}
|
|
212
|
+
function readSourceFile(pkgName, filePath) {
|
|
213
|
+
const relativePath = path.join('packages', 'totem-sdk', 'packages', pkgName, 'src', filePath);
|
|
214
|
+
const absPath = path.join(REPO_ROOT, relativePath);
|
|
215
|
+
const altPath = path.join(REPO_ROOT, 'packages', pkgName, 'src', filePath);
|
|
216
|
+
for (const p of [absPath, altPath]) {
|
|
217
|
+
if (fs.existsSync(p))
|
|
218
|
+
return fs.readFileSync(p, 'utf-8');
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleResourceRead = handleResourceRead;
|
|
4
|
+
exports.listResources = listResources;
|
|
5
|
+
const CONVENTIONS = `# Totem SDK Conventions
|
|
6
|
+
|
|
7
|
+
## Package Structure
|
|
8
|
+
- src/index.ts - barrel exports
|
|
9
|
+
- src/types.ts - all type definitions
|
|
10
|
+
- src/canonical.ts - canonicalJson, toHex, hashCanonical
|
|
11
|
+
- src/ids.ts - ID computation (domain-prefixed SHA3-256)
|
|
12
|
+
- src/errors.ts - error class hierarchy
|
|
13
|
+
- src/__tests__/ - Jest tests
|
|
14
|
+
|
|
15
|
+
## ID Format
|
|
16
|
+
- totem:<package>:<kind>:<sha3-256-hex>
|
|
17
|
+
- Example: totem:ia:proposal:<hex>, totem:gov:proposal:<hex>, edge:device:<hex>
|
|
18
|
+
|
|
19
|
+
## Canonical JSON
|
|
20
|
+
- Recursive deterministic JSON with sorted object keys
|
|
21
|
+
- Used as input to all hashing and signing operations
|
|
22
|
+
- Each package has its own canonicalJson() (no shared util)
|
|
23
|
+
|
|
24
|
+
## Hashing
|
|
25
|
+
- SHA3-256 via @totemsdk/core
|
|
26
|
+
- Domain-prefixed: sha3-256(domain + canonicalJson(data))
|
|
27
|
+
- Domain constants like 'TOTEM_GOVERNANCE_PROPOSAL_V1'
|
|
28
|
+
|
|
29
|
+
## Signing
|
|
30
|
+
- WOTS (Winternitz One-Time Signatures) via @totemsdk/core-wasm WASM
|
|
31
|
+
- @totemsdk/wots-lease for key-use coordination
|
|
32
|
+
|
|
33
|
+
## Error Handling
|
|
34
|
+
- Hierarchical Error subclasses with code strings
|
|
35
|
+
- EdgeOperationResult<T> = { ok: boolean, data?: T, error?: string, errorCode?: string }
|
|
36
|
+
|
|
37
|
+
## Time
|
|
38
|
+
- Unix milliseconds (Date.now())
|
|
39
|
+
- Optional now parameter for determinism in testing
|
|
40
|
+
|
|
41
|
+
## Validation
|
|
42
|
+
- Custom guard functions returning string[]
|
|
43
|
+
- No external schema libraries (no zod/io-ts/ajv)
|
|
44
|
+
|
|
45
|
+
## Async
|
|
46
|
+
- Promise<T> everywhere, typed event emitters, no Observables
|
|
47
|
+
- Action lifecycle: propose -> reserve -> execute -> confirm/fail/unknown`;
|
|
48
|
+
function handleResourceRead(uri, index) {
|
|
49
|
+
if (uri === 'totemsdk://packages') {
|
|
50
|
+
return JSON.stringify(Object.values(index.packages).map(p => ({
|
|
51
|
+
name: p.name,
|
|
52
|
+
version: p.version,
|
|
53
|
+
domain: p.domain,
|
|
54
|
+
description: p.description,
|
|
55
|
+
hasRust: p.hasRust,
|
|
56
|
+
hasGo: p.hasGo,
|
|
57
|
+
hasTests: p.hasTests,
|
|
58
|
+
})), null, 2);
|
|
59
|
+
}
|
|
60
|
+
if (uri === 'totemsdk://conventions')
|
|
61
|
+
return CONVENTIONS;
|
|
62
|
+
if (uri === 'totemsdk://domain-map') {
|
|
63
|
+
return JSON.stringify(index.domainMap, null, 2);
|
|
64
|
+
}
|
|
65
|
+
const pkgMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)$/);
|
|
66
|
+
if (pkgMatch) {
|
|
67
|
+
const pkg = index.packages[pkgMatch[1]];
|
|
68
|
+
return pkg ? JSON.stringify(pkg, null, 2) : null;
|
|
69
|
+
}
|
|
70
|
+
const pkgExportsMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)\/exports$/);
|
|
71
|
+
if (pkgExportsMatch) {
|
|
72
|
+
const pkg = index.packages[pkgExportsMatch[1]];
|
|
73
|
+
return pkg ? JSON.stringify(pkg.exports, null, 2) : null;
|
|
74
|
+
}
|
|
75
|
+
const pkgDepsMatch = uri.match(/^totemsdk:\/\/packages\/([^/]+)\/dependencies$/);
|
|
76
|
+
if (pkgDepsMatch) {
|
|
77
|
+
const pkg = index.packages[pkgDepsMatch[1]];
|
|
78
|
+
if (!pkg)
|
|
79
|
+
return null;
|
|
80
|
+
return JSON.stringify({ dependencies: pkg.dependencies, devDependencies: pkg.devDependencies }, null, 2);
|
|
81
|
+
}
|
|
82
|
+
const domainMatch = uri.match(/^totemsdk:\/\/packages\/by-domain\/(.+)$/);
|
|
83
|
+
if (domainMatch) {
|
|
84
|
+
const pkgs = index.domainMap[domainMatch[1]];
|
|
85
|
+
return pkgs ? JSON.stringify(pkgs.map(n => index.packages[n]).filter(Boolean), null, 2) : null;
|
|
86
|
+
}
|
|
87
|
+
const symbolMatch = uri.match(/^totemsdk:\/\/symbol\/(.+)$/);
|
|
88
|
+
if (symbolMatch) {
|
|
89
|
+
const entries = index.symbolIndex[symbolMatch[1]];
|
|
90
|
+
return entries ? JSON.stringify(entries, null, 2) : null;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
function listResources(index) {
|
|
95
|
+
const resources = [
|
|
96
|
+
{ uri: 'totemsdk://packages', name: 'All Packages', description: 'List of all 53 SDK packages with metadata' },
|
|
97
|
+
{ uri: 'totemsdk://conventions', name: 'Coding Conventions', description: 'Totem SDK coding conventions and patterns' },
|
|
98
|
+
{ uri: 'totemsdk://domain-map', name: 'Domain Map', description: 'Packages grouped by domain layer' },
|
|
99
|
+
];
|
|
100
|
+
for (const [domain, pkgs] of Object.entries(index.domainMap)) {
|
|
101
|
+
resources.push({
|
|
102
|
+
uri: `totemsdk://packages/by-domain/${domain}`,
|
|
103
|
+
name: `Domain: ${domain}`,
|
|
104
|
+
description: `${pkgs.length} packages in ${domain}`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
for (const [name, pkg] of Object.entries(index.packages)) {
|
|
108
|
+
resources.push({
|
|
109
|
+
uri: `totemsdk://packages/${encodeURIComponent(name)}`,
|
|
110
|
+
name: `Package: ${name}`,
|
|
111
|
+
description: pkg.description,
|
|
112
|
+
});
|
|
113
|
+
resources.push({
|
|
114
|
+
uri: `totemsdk://packages/${encodeURIComponent(name)}/exports`,
|
|
115
|
+
name: `Exports: ${name}`,
|
|
116
|
+
description: `Exported symbols from ${name}`,
|
|
117
|
+
});
|
|
118
|
+
resources.push({
|
|
119
|
+
uri: `totemsdk://packages/${encodeURIComponent(name)}/dependencies`,
|
|
120
|
+
name: `Dependencies: ${name}`,
|
|
121
|
+
description: `Dependencies of ${name}`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const topSymbols = Object.entries(index.symbolIndex)
|
|
125
|
+
.filter(([_, v]) => v.length <= 3)
|
|
126
|
+
.slice(0, 200);
|
|
127
|
+
for (const [symbol, entries] of topSymbols) {
|
|
128
|
+
resources.push({
|
|
129
|
+
uri: `totemsdk://symbol/${symbol}`,
|
|
130
|
+
name: `Symbol: ${symbol}`,
|
|
131
|
+
description: `Found in ${entries.map(e => e.package).join(', ')}`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return resources;
|
|
135
|
+
}
|
package/dist/tools.d.ts
ADDED
package/dist/tools.js
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleToolCall = handleToolCall;
|
|
4
|
+
function handleToolCall(name, args, index) {
|
|
5
|
+
switch (name) {
|
|
6
|
+
case 'search-symbol': return searchSymbol(args, index);
|
|
7
|
+
case 'find-type': return findType(args, index);
|
|
8
|
+
case 'dependency-graph': return dependencyGraph(args, index);
|
|
9
|
+
case 'validate-import': return validateImport(args, index);
|
|
10
|
+
case 'scaffold-adapter': return scaffoldAdapter(args);
|
|
11
|
+
case 'scaffold-package': return scaffoldPackage(args);
|
|
12
|
+
case 'package-stats': return packageStats(args, index);
|
|
13
|
+
case 'list-exports': return listExports(args, index);
|
|
14
|
+
default: return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function searchSymbol(args, index) {
|
|
18
|
+
const query = (args.query || '').toLowerCase();
|
|
19
|
+
if (!query)
|
|
20
|
+
return { content: [{ type: 'text', text: 'query is required' }], isError: true };
|
|
21
|
+
const results = [];
|
|
22
|
+
for (const [symbol, entries] of Object.entries(index.symbolIndex)) {
|
|
23
|
+
if (symbol.toLowerCase().includes(query)) {
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
results.push({ symbol, package: entry.package, kind: entry.kind });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
results.sort((a, b) => a.symbol.localeCompare(b.symbol));
|
|
30
|
+
if (results.length === 0) {
|
|
31
|
+
return { content: [{ type: 'text', text: `No symbols found matching '${query}'` }] };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: 'text',
|
|
36
|
+
text: `Found ${results.length} match(es) for '${query}':\n\n` +
|
|
37
|
+
results.map(r => ` ${r.symbol} (${r.kind}) — ${r.package}`).join('\n'),
|
|
38
|
+
}],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function findType(args, index) {
|
|
42
|
+
const pattern = (args.pattern || '').toLowerCase();
|
|
43
|
+
if (!pattern)
|
|
44
|
+
return { content: [{ type: 'text', text: 'pattern is required' }], isError: true };
|
|
45
|
+
const results = [];
|
|
46
|
+
for (const [pkgName, pkg] of Object.entries(index.packages)) {
|
|
47
|
+
for (const t of pkg.exports.interfaces) {
|
|
48
|
+
if (t.toLowerCase().includes(pattern))
|
|
49
|
+
results.push({ package: pkgName, type: t, kind: 'interface' });
|
|
50
|
+
}
|
|
51
|
+
for (const t of pkg.exports.types) {
|
|
52
|
+
if (t.toLowerCase().includes(pattern))
|
|
53
|
+
results.push({ package: pkgName, type: t, kind: 'type' });
|
|
54
|
+
}
|
|
55
|
+
for (const c of pkg.exports.classes) {
|
|
56
|
+
if (c.toLowerCase().includes(pattern))
|
|
57
|
+
results.push({ package: pkgName, type: c, kind: 'class' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
results.sort((a, b) => a.type.localeCompare(b.type));
|
|
61
|
+
if (results.length === 0) {
|
|
62
|
+
return { content: [{ type: 'text', text: `No types found matching '${pattern}'` }] };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
content: [{
|
|
66
|
+
type: 'text',
|
|
67
|
+
text: `Found ${results.length} type(s) for '${pattern}':\n\n` +
|
|
68
|
+
results.map(r => ` ${r.type} (${r.kind}) — ${r.package}`).join('\n'),
|
|
69
|
+
}],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function dependencyGraph(args, index) {
|
|
73
|
+
const pkgName = args.package;
|
|
74
|
+
const direction = args.direction || 'out';
|
|
75
|
+
if (!pkgName || !index.packages[pkgName]) {
|
|
76
|
+
return { content: [{ type: 'text', text: `Package '${pkgName}' not found` }], isError: true };
|
|
77
|
+
}
|
|
78
|
+
const pkg = index.packages[pkgName];
|
|
79
|
+
if (direction === 'out' || direction === 'all') {
|
|
80
|
+
const totemDeps = pkg.dependencies.filter(d => d.startsWith('@totemsdk/'));
|
|
81
|
+
const externalDeps = pkg.dependencies.filter(d => !d.startsWith('@totemsdk/'));
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: 'text',
|
|
85
|
+
text: `# ${pkgName} — Outbound Dependencies\n\n` +
|
|
86
|
+
(totemDeps.length ? `**@totemsdk/* deps:**\n${totemDeps.map(d => ` - ${d}`).join('\n')}\n\n` : '') +
|
|
87
|
+
(externalDeps.length ? `**External deps:**\n${externalDeps.map(d => ` - ${d}`).join('\n')}\n` : '_(no external deps)_'),
|
|
88
|
+
}],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (direction === 'in' || direction === 'all') {
|
|
92
|
+
const dependents = [];
|
|
93
|
+
for (const [name, other] of Object.entries(index.packages)) {
|
|
94
|
+
if (other.dependencies.includes(pkgName))
|
|
95
|
+
dependents.push(name);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
content: [{
|
|
99
|
+
type: 'text',
|
|
100
|
+
text: `# ${pkgName} — Inbound Dependents\n\n` +
|
|
101
|
+
(dependents.length ? dependents.map(d => ` - ${d}`).join('\n') : '_(no dependents)_'),
|
|
102
|
+
}],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return { content: [{ type: 'text', text: 'Invalid direction. Use "in", "out", or "all".' }], isError: true };
|
|
106
|
+
}
|
|
107
|
+
function validateImport(args, index) {
|
|
108
|
+
const { from: fromPkg, to: toPkg, symbol } = args;
|
|
109
|
+
if (!fromPkg || !toPkg) {
|
|
110
|
+
return { content: [{ type: 'text', text: 'from and to package names are required' }], isError: true };
|
|
111
|
+
}
|
|
112
|
+
const fromPkgData = index.packages[fromPkg];
|
|
113
|
+
const toPkgData = index.packages[toPkg];
|
|
114
|
+
if (!fromPkgData)
|
|
115
|
+
return { content: [{ type: 'text', text: `Source package '${fromPkg}' not found` }], isError: true };
|
|
116
|
+
if (!toPkgData)
|
|
117
|
+
return { content: [{ type: 'text', text: `Target package '${toPkg}' not found` }], isError: true };
|
|
118
|
+
const hasDep = fromPkgData.dependencies.includes(toPkg);
|
|
119
|
+
const result = { valid: hasDep, importPath: toPkg, symbolFound: false };
|
|
120
|
+
if (hasDep) {
|
|
121
|
+
if (symbol) {
|
|
122
|
+
const allExports = [
|
|
123
|
+
...toPkgData.exports.functions,
|
|
124
|
+
...toPkgData.exports.types,
|
|
125
|
+
...toPkgData.exports.classes,
|
|
126
|
+
...toPkgData.exports.interfaces,
|
|
127
|
+
...toPkgData.exports.consts,
|
|
128
|
+
];
|
|
129
|
+
result.symbolFound = allExports.includes(symbol);
|
|
130
|
+
if (!result.symbolFound) {
|
|
131
|
+
result.valid = false;
|
|
132
|
+
result.reason = `'${toPkg}' does not export '${symbol}'`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
result.reason = `'${fromPkg}' does not depend on '${toPkg}'`;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
content: [{
|
|
141
|
+
type: 'text',
|
|
142
|
+
text: JSON.stringify(result, null, 2),
|
|
143
|
+
}],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function scaffoldAdapter(args) {
|
|
147
|
+
const { name, protocol, commands } = args;
|
|
148
|
+
if (!name || !protocol) {
|
|
149
|
+
return { content: [{ type: 'text', text: 'name and protocol are required' }], isError: true };
|
|
150
|
+
}
|
|
151
|
+
const cmdList = Array.isArray(commands) ? commands : ['connect', 'disconnect', 'read', 'write'];
|
|
152
|
+
const files = [];
|
|
153
|
+
files.push({
|
|
154
|
+
path: `src/index.ts`,
|
|
155
|
+
content: [
|
|
156
|
+
`export type { ${protocol}TransportPort } from './transport.js'`,
|
|
157
|
+
`export { create${protocol}Gateway } from './gateway.js'`,
|
|
158
|
+
`export type { ${protocol}GatewayConfig, ${protocol}Gateway } from './gateway.js'`,
|
|
159
|
+
`export { create${protocol}SensorBridge } from './sensor-bridge.js'`,
|
|
160
|
+
`export type { ${protocol}SensorBinding, ${protocol}SensorBridgeConfig } from './sensor-bridge.js'`,
|
|
161
|
+
``,
|
|
162
|
+
].join('\n'),
|
|
163
|
+
});
|
|
164
|
+
files.push({
|
|
165
|
+
path: `src/transport.ts`,
|
|
166
|
+
content: [
|
|
167
|
+
`export interface ${protocol}TransportPort {`,
|
|
168
|
+
...cmdList.map(c => ` ${c}(...args: unknown[]): Promise<unknown>;`),
|
|
169
|
+
` onError(handler: (err: Error) => void): () => void;`,
|
|
170
|
+
`}`,
|
|
171
|
+
``,
|
|
172
|
+
].join('\n'),
|
|
173
|
+
});
|
|
174
|
+
files.push({
|
|
175
|
+
path: `src/gateway.ts`,
|
|
176
|
+
content: [
|
|
177
|
+
`import type { ${protocol}TransportPort } from './transport.js'`,
|
|
178
|
+
`import type { EdgeRuntime } from '@totemsdk/edge'`,
|
|
179
|
+
``,
|
|
180
|
+
`export interface ${protocol}GatewayConfig {`,
|
|
181
|
+
` runtime: EdgeRuntime`,
|
|
182
|
+
` transport: ${protocol}TransportPort`,
|
|
183
|
+
`}`,
|
|
184
|
+
``,
|
|
185
|
+
`export interface ${protocol}Gateway {`,
|
|
186
|
+
` start(): Promise<void>`,
|
|
187
|
+
` stop(): Promise<void>`,
|
|
188
|
+
`}`,
|
|
189
|
+
``,
|
|
190
|
+
`export function create${protocol}Gateway(config: ${protocol}GatewayConfig): ${protocol}Gateway {`,
|
|
191
|
+
` return {`,
|
|
192
|
+
` async start() { /* TODO: implement */ },`,
|
|
193
|
+
` async stop() { /* TODO: implement */ },`,
|
|
194
|
+
` }`,
|
|
195
|
+
`}`,
|
|
196
|
+
``,
|
|
197
|
+
].join('\n'),
|
|
198
|
+
});
|
|
199
|
+
files.push({
|
|
200
|
+
path: `src/sensor-bridge.ts`,
|
|
201
|
+
content: [
|
|
202
|
+
`import type { ${protocol}TransportPort } from './transport.js'`,
|
|
203
|
+
`import type { ${protocol}Gateway } from './gateway.js'`,
|
|
204
|
+
`import type { EdgeRuntime } from '@totemsdk/edge'`,
|
|
205
|
+
``,
|
|
206
|
+
`export interface ${protocol}SensorBinding {`,
|
|
207
|
+
` sensorId: string`,
|
|
208
|
+
` intervalMs: number`,
|
|
209
|
+
` dataType?: string`,
|
|
210
|
+
` unit?: string`,
|
|
211
|
+
`}`,
|
|
212
|
+
``,
|
|
213
|
+
`export interface ${protocol}SensorBridgeConfig {`,
|
|
214
|
+
` runtime: EdgeRuntime`,
|
|
215
|
+
` transport: ${protocol}TransportPort`,
|
|
216
|
+
` gateway: ${protocol}Gateway`,
|
|
217
|
+
` bindings: ${protocol}SensorBinding[]`,
|
|
218
|
+
`}`,
|
|
219
|
+
``,
|
|
220
|
+
`export interface ${protocol}SensorBridge {`,
|
|
221
|
+
` start(): Promise<void>`,
|
|
222
|
+
` stop(): Promise<void>`,
|
|
223
|
+
`}`,
|
|
224
|
+
``,
|
|
225
|
+
`export function create${protocol}SensorBridge(config: ${protocol}SensorBridgeConfig): ${protocol}SensorBridge {`,
|
|
226
|
+
` return {`,
|
|
227
|
+
` async start() { /* TODO: implement */ },`,
|
|
228
|
+
` async stop() { /* TODO: implement */ },`,
|
|
229
|
+
` }`,
|
|
230
|
+
`}`,
|
|
231
|
+
``,
|
|
232
|
+
].join('\n'),
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
content: [{
|
|
236
|
+
type: 'text',
|
|
237
|
+
text: `# Scaffolded ${protocol} Edge Adapter\n\nCreated ${files.length} files:\n\n` +
|
|
238
|
+
files.map(f => `**${f.path}**\n\`\`\`typescript\n${f.content}\n\`\`\``).join('\n\n'),
|
|
239
|
+
}],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function scaffoldPackage(args) {
|
|
243
|
+
const { name, deps } = args;
|
|
244
|
+
if (!name)
|
|
245
|
+
return { content: [{ type: 'text', text: 'name is required' }], isError: true };
|
|
246
|
+
const depList = Array.isArray(deps) ? deps : ['@totemsdk/core'];
|
|
247
|
+
const files = [];
|
|
248
|
+
files.push({
|
|
249
|
+
path: `package.json`,
|
|
250
|
+
content: JSON.stringify({
|
|
251
|
+
name: `@totemsdk/${name}`,
|
|
252
|
+
version: '0.1.0',
|
|
253
|
+
description: `TODO: describe ${name}`,
|
|
254
|
+
main: 'dist/index.js',
|
|
255
|
+
types: 'dist/index.d.ts',
|
|
256
|
+
exports: {
|
|
257
|
+
'.': {
|
|
258
|
+
types: './dist/index.d.ts',
|
|
259
|
+
require: './dist/index.js',
|
|
260
|
+
import: './dist/index.js',
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
scripts: { build: 'tsc', clean: 'rm -rf dist', test: 'jest --passWithNoTests' },
|
|
264
|
+
files: ['dist', 'README.md', 'LICENSE'],
|
|
265
|
+
dependencies: Object.fromEntries(depList.map(d => [d, '^0.1.0'])),
|
|
266
|
+
devDependencies: {
|
|
267
|
+
'@types/jest': '^30.0.0',
|
|
268
|
+
'@types/node': '^20.0.0',
|
|
269
|
+
jest: '^30.4.2',
|
|
270
|
+
typescript: '^7.0.2',
|
|
271
|
+
},
|
|
272
|
+
publishConfig: { access: 'public' },
|
|
273
|
+
license: 'MIT',
|
|
274
|
+
}, null, 2),
|
|
275
|
+
});
|
|
276
|
+
files.push({
|
|
277
|
+
path: `tsconfig.json`,
|
|
278
|
+
content: JSON.stringify({
|
|
279
|
+
compilerOptions: {
|
|
280
|
+
target: 'ES2020',
|
|
281
|
+
module: 'commonjs',
|
|
282
|
+
declaration: true,
|
|
283
|
+
outDir: './dist',
|
|
284
|
+
rootDir: './src',
|
|
285
|
+
strict: true,
|
|
286
|
+
esModuleInterop: true,
|
|
287
|
+
skipLibCheck: true,
|
|
288
|
+
moduleResolution: 'bundler',
|
|
289
|
+
resolveJsonModule: true,
|
|
290
|
+
},
|
|
291
|
+
include: ['src/**/*'],
|
|
292
|
+
exclude: ['node_modules', 'dist', '**/*.test.ts'],
|
|
293
|
+
}, null, 2),
|
|
294
|
+
});
|
|
295
|
+
files.push({
|
|
296
|
+
path: `src/index.ts`,
|
|
297
|
+
content: [
|
|
298
|
+
`export {}`,
|
|
299
|
+
``,
|
|
300
|
+
].join('\n'),
|
|
301
|
+
});
|
|
302
|
+
files.push({
|
|
303
|
+
path: `src/types.ts`,
|
|
304
|
+
content: [
|
|
305
|
+
`export {}`,
|
|
306
|
+
``,
|
|
307
|
+
].join('\n'),
|
|
308
|
+
});
|
|
309
|
+
files.push({
|
|
310
|
+
path: `src/canonical.ts`,
|
|
311
|
+
content: [
|
|
312
|
+
`import { sha3_256 } from '@totemsdk/core'`,
|
|
313
|
+
``,
|
|
314
|
+
`export function toHex(bytes: Uint8Array): string {`,
|
|
315
|
+
` return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')`,
|
|
316
|
+
`}`,
|
|
317
|
+
``,
|
|
318
|
+
`export function canonicalJson(value: unknown): string {`,
|
|
319
|
+
` if (value === null || typeof value !== 'object') return JSON.stringify(value)`,
|
|
320
|
+
` if (Array.isArray(value)) return '[' + value.map(canonicalJson).join(',') + ']'`,
|
|
321
|
+
` const obj = value as Record<string, unknown>`,
|
|
322
|
+
` const keys = Object.keys(obj).sort()`,
|
|
323
|
+
` const pairs = keys.map(k => JSON.stringify(k) + ':' + canonicalJson(obj[k]))`,
|
|
324
|
+
` return '{' + pairs.join(',') + '}'`,
|
|
325
|
+
`}`,
|
|
326
|
+
``,
|
|
327
|
+
`export function hashCanonical(domain: string, value: unknown): string {`,
|
|
328
|
+
` return toHex(sha3_256(new TextEncoder().encode(domain + canonicalJson(value))))`,
|
|
329
|
+
`}`,
|
|
330
|
+
``,
|
|
331
|
+
].join('\n'),
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
content: [{
|
|
335
|
+
type: 'text',
|
|
336
|
+
text: `# Scaffolded @totemsdk/${name}\n\nCreated ${files.length} files in ${name}/:\n\n` +
|
|
337
|
+
files.map(f => `**${f.path}**\n\`\`\`\n${f.content}\n\`\`\``).join('\n\n'),
|
|
338
|
+
}],
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function packageStats(args, index) {
|
|
342
|
+
const pkgName = args.name;
|
|
343
|
+
if (!pkgName || !index.packages[pkgName]) {
|
|
344
|
+
return { content: [{ type: 'text', text: `Package '${pkgName}' not found` }], isError: true };
|
|
345
|
+
}
|
|
346
|
+
const pkg = index.packages[pkgName];
|
|
347
|
+
const totalExports = pkg.exports.functions.length + pkg.exports.types.length +
|
|
348
|
+
pkg.exports.classes.length + pkg.exports.interfaces.length + pkg.exports.consts.length;
|
|
349
|
+
const totemDeps = pkg.dependencies.filter(d => d.startsWith('@totemsdk/'));
|
|
350
|
+
const externalDeps = pkg.dependencies.filter(d => !d.startsWith('@totemsdk/'));
|
|
351
|
+
return {
|
|
352
|
+
content: [{
|
|
353
|
+
type: 'text',
|
|
354
|
+
text: [
|
|
355
|
+
`# ${pkgName} v${pkg.version}`,
|
|
356
|
+
``,
|
|
357
|
+
`**Domain:** ${pkg.domain}`,
|
|
358
|
+
`**Description:** ${pkg.description}`,
|
|
359
|
+
`**Directory:** ${pkg.dir}`,
|
|
360
|
+
``,
|
|
361
|
+
`| Metric | Value |`,
|
|
362
|
+
`|--------|-------|`,
|
|
363
|
+
`| Functions | ${pkg.exports.functions.length} |`,
|
|
364
|
+
`| Types (named) | ${pkg.exports.types.length} |`,
|
|
365
|
+
`| Interfaces | ${pkg.exports.interfaces.length} |`,
|
|
366
|
+
`| Classes | ${pkg.exports.classes.length} |`,
|
|
367
|
+
`| Constants | ${pkg.exports.consts.length} |`,
|
|
368
|
+
`| **Total exports** | **${totalExports}** |`,
|
|
369
|
+
`| Rust/WASM | ${pkg.hasRust ? 'Yes' : 'No'} |`,
|
|
370
|
+
`| Go | ${pkg.hasGo ? 'Yes' : 'No'} |`,
|
|
371
|
+
`| Tests | ${pkg.hasTests ? 'Yes' : 'No'} |`,
|
|
372
|
+
`| @totemsdk/* deps | ${totemDeps.length} |`,
|
|
373
|
+
`| External deps | ${externalDeps.length} |`,
|
|
374
|
+
``,
|
|
375
|
+
].join('\n'),
|
|
376
|
+
}],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function listExports(args, index) {
|
|
380
|
+
const pkgName = args.package;
|
|
381
|
+
const kind = (args.kind || '').toLowerCase();
|
|
382
|
+
const filter = (args.filter || '').toLowerCase();
|
|
383
|
+
if (!pkgName || !index.packages[pkgName]) {
|
|
384
|
+
return { content: [{ type: 'text', text: `Package '${pkgName}' not found` }], isError: true };
|
|
385
|
+
}
|
|
386
|
+
const pkg = index.packages[pkgName];
|
|
387
|
+
const sections = [];
|
|
388
|
+
if (!kind || kind === 'function') {
|
|
389
|
+
const items = filter ? pkg.exports.functions.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.functions;
|
|
390
|
+
if (items.length)
|
|
391
|
+
sections.push(`**Functions (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
|
|
392
|
+
}
|
|
393
|
+
if (!kind || kind === 'interface') {
|
|
394
|
+
const items = filter ? pkg.exports.interfaces.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.interfaces;
|
|
395
|
+
if (items.length)
|
|
396
|
+
sections.push(`**Interfaces (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
|
|
397
|
+
}
|
|
398
|
+
if (!kind || kind === 'type') {
|
|
399
|
+
const items = filter ? pkg.exports.types.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.types;
|
|
400
|
+
if (items.length)
|
|
401
|
+
sections.push(`**Named Types (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
|
|
402
|
+
}
|
|
403
|
+
if (!kind || kind === 'class') {
|
|
404
|
+
const items = filter ? pkg.exports.classes.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.classes;
|
|
405
|
+
if (items.length)
|
|
406
|
+
sections.push(`**Classes (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
|
|
407
|
+
}
|
|
408
|
+
if (!kind || kind === 'const') {
|
|
409
|
+
const items = filter ? pkg.exports.consts.filter(s => s.toLowerCase().includes(filter)) : pkg.exports.consts;
|
|
410
|
+
if (items.length)
|
|
411
|
+
sections.push(`**Constants (${items.length}):**\n` + items.map(s => ` - \`${s}\``).join('\n'));
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
content: [{ type: 'text', text: sections.join('\n\n') || `No exports found for '${pkgName}'` }],
|
|
415
|
+
};
|
|
416
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface PackageIndex {
|
|
2
|
+
name: string;
|
|
3
|
+
dir: string;
|
|
4
|
+
version: string;
|
|
5
|
+
description: string;
|
|
6
|
+
dependencies: string[];
|
|
7
|
+
devDependencies: string[];
|
|
8
|
+
hasRust: boolean;
|
|
9
|
+
hasGo: boolean;
|
|
10
|
+
hasTests: boolean;
|
|
11
|
+
exports: PackageExports;
|
|
12
|
+
domain: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PackageExports {
|
|
15
|
+
functions: string[];
|
|
16
|
+
types: string[];
|
|
17
|
+
classes: string[];
|
|
18
|
+
interfaces: string[];
|
|
19
|
+
consts: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface SymbolEntry {
|
|
22
|
+
package: string;
|
|
23
|
+
kind: 'function' | 'type' | 'class' | 'interface' | 'const' | 'variable';
|
|
24
|
+
}
|
|
25
|
+
export interface DomainMap {
|
|
26
|
+
[domain: string]: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface SdkIndex {
|
|
29
|
+
generatedAt: number;
|
|
30
|
+
packages: {
|
|
31
|
+
[name: string]: PackageIndex;
|
|
32
|
+
};
|
|
33
|
+
symbolIndex: {
|
|
34
|
+
[symbol: string]: SymbolEntry[];
|
|
35
|
+
};
|
|
36
|
+
domainMap: DomainMap;
|
|
37
|
+
}
|
|
38
|
+
export interface ToolResponse {
|
|
39
|
+
content: Array<{
|
|
40
|
+
type: string;
|
|
41
|
+
text: string;
|
|
42
|
+
}>;
|
|
43
|
+
isError?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface ValidImportResult {
|
|
46
|
+
valid: boolean;
|
|
47
|
+
importPath?: string;
|
|
48
|
+
symbolFound?: boolean;
|
|
49
|
+
reason?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface ScaffoldResult {
|
|
52
|
+
files: Array<{
|
|
53
|
+
path: string;
|
|
54
|
+
content: string;
|
|
55
|
+
}>;
|
|
56
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@totemsdk/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server exposing the full Totem SDK package set — metadata, types, exports, dependency graphs, and scaffolding tools for 53 packages",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"totemsdk-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"require": "./dist/index.js",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepare": "",
|
|
20
|
+
"clean": "rm -rf dist",
|
|
21
|
+
"start": "node dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"typescript": "^7.0.2"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"author": "Totem SDK",
|
|
39
|
+
"homepage": "https://totem.ing",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/totem-sdk/totem-sdk/issues"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/totem-sdk/totem-sdk.git",
|
|
46
|
+
"directory": "packages/totem-sdk/packages/mcp-server"
|
|
47
|
+
},
|
|
48
|
+
"keywords": ["totem", "totemsdk", "mcp", "model-context-protocol", "sdk-index"]
|
|
49
|
+
}
|