@kya-os/mcp-i 0.1.0-alpha.2.3 → 0.1.0-alpha.2.4
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/README.md +192 -216
- package/dist/auto.d.ts +0 -12
- package/dist/auto.js +3 -14
- package/dist/crypto.d.ts +10 -26
- package/dist/crypto.js +117 -37
- package/dist/dev-helper.d.ts +3 -0
- package/dist/dev-helper.js +54 -0
- package/dist/encrypted-storage.d.ts +11 -0
- package/dist/encrypted-storage.js +73 -0
- package/dist/index.d.ts +29 -106
- package/dist/index.js +225 -392
- package/dist/logger.d.ts +32 -0
- package/dist/logger.js +66 -0
- package/dist/registry/index.d.ts +0 -31
- package/dist/registry/index.js +2 -42
- package/dist/registry/knowthat.d.ts +3 -18
- package/dist/registry/knowthat.js +10 -35
- package/dist/rotation.d.ts +35 -0
- package/dist/rotation.js +102 -0
- package/dist/storage.d.ts +41 -0
- package/dist/storage.js +163 -0
- package/dist/transport.d.ts +34 -0
- package/dist/transport.js +207 -0
- package/dist/types.d.ts +72 -99
- package/dist/types.js +0 -4
- package/package.json +16 -6
- package/dist/__tests__/challenge-response.test.d.ts +0 -5
- package/dist/__tests__/challenge-response.test.d.ts.map +0 -1
- package/dist/__tests__/challenge-response.test.js +0 -218
- package/dist/__tests__/challenge-response.test.js.map +0 -1
- package/dist/__tests__/crypto.test.d.ts +0 -5
- package/dist/__tests__/crypto.test.d.ts.map +0 -1
- package/dist/__tests__/crypto.test.js +0 -153
- package/dist/__tests__/crypto.test.js.map +0 -1
- package/dist/auto.d.ts.map +0 -1
- package/dist/auto.js.map +0 -1
- package/dist/crypto.d.ts.map +0 -1
- package/dist/crypto.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/registry/cursor.d.ts +0 -25
- package/dist/registry/cursor.d.ts.map +0 -1
- package/dist/registry/cursor.js +0 -108
- package/dist/registry/cursor.js.map +0 -1
- package/dist/registry/glama.d.ts +0 -25
- package/dist/registry/glama.d.ts.map +0 -1
- package/dist/registry/glama.js +0 -111
- package/dist/registry/glama.js.map +0 -1
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/registry/knowthat.d.ts.map +0 -1
- package/dist/registry/knowthat.js.map +0 -1
- package/dist/registry/smithery.d.ts +0 -29
- package/dist/registry/smithery.d.ts.map +0 -1
- package/dist/registry/smithery.js +0 -119
- package/dist/registry/smithery.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,27 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @kya-os/mcp-i - Ultra-light MCP Identity auto-registration
|
|
4
|
-
*
|
|
5
|
-
* Enable any MCP server to get a verifiable identity with just 2 lines of code:
|
|
6
|
-
*
|
|
7
|
-
* ```typescript
|
|
8
|
-
* import "@kya-os/mcp-i/auto"; // That's it! Your server now has identity
|
|
9
|
-
* ```
|
|
10
|
-
*
|
|
11
|
-
* Or with configuration:
|
|
12
|
-
* ```typescript
|
|
13
|
-
* import { enableMCPIdentity } from "@kya-os/mcp-i";
|
|
14
|
-
* await enableMCPIdentity({ name: "My Amazing Agent" });
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* Future multi-registry support:
|
|
18
|
-
* ```typescript
|
|
19
|
-
* await enableMCPIdentity({
|
|
20
|
-
* name: "My Agent",
|
|
21
|
-
* // Additional registries will be supported as directories adopt MCP-I
|
|
22
|
-
* });
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
26
3
|
if (k2 === undefined) k2 = k;
|
|
27
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -58,123 +35,113 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
58
35
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
59
36
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
60
37
|
};
|
|
61
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
62
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
63
|
-
};
|
|
64
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
65
|
-
exports.MCPIdentity = exports.resolveRegistries = exports.REGISTRY_TIERS = exports.RegistryFactory = void 0;
|
|
39
|
+
exports.MCPIdentity = exports.showAgentStatus = exports.initWithDevExperience = exports.KeyRotationManager = exports.RuntimeDetector = exports.TransportFactory = exports.FileStorage = exports.MemoryStorage = exports.StorageFactory = exports.SilentLogger = exports.ConsoleLogger = exports.LoggerFactory = exports.resolveRegistries = exports.REGISTRY_TIERS = exports.RegistryFactory = void 0;
|
|
66
40
|
exports.enableMCPIdentity = enableMCPIdentity;
|
|
67
|
-
|
|
68
|
-
const fs = __importStar(require("fs"));
|
|
69
|
-
const path = __importStar(require("path"));
|
|
41
|
+
exports.createMCPMiddleware = createMCPMiddleware;
|
|
70
42
|
const crypto = __importStar(require("./crypto"));
|
|
71
|
-
const
|
|
72
|
-
|
|
43
|
+
const storage_1 = require("./storage");
|
|
44
|
+
const transport_1 = require("./transport");
|
|
45
|
+
const logger_1 = require("./logger");
|
|
46
|
+
const rotation_1 = require("./rotation");
|
|
73
47
|
__exportStar(require("./types"), exports);
|
|
74
|
-
var
|
|
75
|
-
Object.defineProperty(exports, "RegistryFactory", { enumerable: true, get: function () { return
|
|
76
|
-
Object.defineProperty(exports, "REGISTRY_TIERS", { enumerable: true, get: function () { return
|
|
77
|
-
Object.defineProperty(exports, "resolveRegistries", { enumerable: true, get: function () { return
|
|
78
|
-
|
|
48
|
+
var registry_1 = require("./registry");
|
|
49
|
+
Object.defineProperty(exports, "RegistryFactory", { enumerable: true, get: function () { return registry_1.RegistryFactory; } });
|
|
50
|
+
Object.defineProperty(exports, "REGISTRY_TIERS", { enumerable: true, get: function () { return registry_1.REGISTRY_TIERS; } });
|
|
51
|
+
Object.defineProperty(exports, "resolveRegistries", { enumerable: true, get: function () { return registry_1.resolveRegistries; } });
|
|
52
|
+
var logger_2 = require("./logger");
|
|
53
|
+
Object.defineProperty(exports, "LoggerFactory", { enumerable: true, get: function () { return logger_2.LoggerFactory; } });
|
|
54
|
+
Object.defineProperty(exports, "ConsoleLogger", { enumerable: true, get: function () { return logger_2.ConsoleLogger; } });
|
|
55
|
+
Object.defineProperty(exports, "SilentLogger", { enumerable: true, get: function () { return logger_2.SilentLogger; } });
|
|
56
|
+
var storage_2 = require("./storage");
|
|
57
|
+
Object.defineProperty(exports, "StorageFactory", { enumerable: true, get: function () { return storage_2.StorageFactory; } });
|
|
58
|
+
Object.defineProperty(exports, "MemoryStorage", { enumerable: true, get: function () { return storage_2.MemoryStorage; } });
|
|
59
|
+
Object.defineProperty(exports, "FileStorage", { enumerable: true, get: function () { return storage_2.FileStorage; } });
|
|
60
|
+
var transport_2 = require("./transport");
|
|
61
|
+
Object.defineProperty(exports, "TransportFactory", { enumerable: true, get: function () { return transport_2.TransportFactory; } });
|
|
62
|
+
Object.defineProperty(exports, "RuntimeDetector", { enumerable: true, get: function () { return transport_2.RuntimeDetector; } });
|
|
63
|
+
var rotation_2 = require("./rotation");
|
|
64
|
+
Object.defineProperty(exports, "KeyRotationManager", { enumerable: true, get: function () { return rotation_2.KeyRotationManager; } });
|
|
65
|
+
var dev_helper_1 = require("./dev-helper");
|
|
66
|
+
Object.defineProperty(exports, "initWithDevExperience", { enumerable: true, get: function () { return dev_helper_1.initWithDevExperience; } });
|
|
67
|
+
Object.defineProperty(exports, "showAgentStatus", { enumerable: true, get: function () { return dev_helper_1.showAgentStatus; } });
|
|
79
68
|
let globalIdentity = null;
|
|
80
69
|
class MCPIdentity {
|
|
81
70
|
constructor(identity, options = {}) {
|
|
82
71
|
this.usedNonces = new Set();
|
|
83
|
-
|
|
72
|
+
if (options.logger) {
|
|
73
|
+
logger_1.LoggerFactory.setLogger(options.logger);
|
|
74
|
+
}
|
|
75
|
+
else if (options.logLevel && options.logLevel !== 'silent') {
|
|
76
|
+
logger_1.LoggerFactory.setLogger(logger_1.LoggerFactory.createConsoleLogger(options.logLevel));
|
|
77
|
+
}
|
|
78
|
+
this.logger = (0, logger_1.getLogger)();
|
|
84
79
|
this.did = identity.did;
|
|
85
80
|
this.publicKey = identity.publicKey;
|
|
86
81
|
this.privateKey = identity.privateKey;
|
|
87
|
-
this.
|
|
82
|
+
this.encryptionPassword = options.encryptionPassword;
|
|
83
|
+
this.timestampTolerance = options.timestampTolerance || 60000;
|
|
88
84
|
this.enableNonceTracking = options.enableNonceTracking !== false;
|
|
89
|
-
|
|
90
|
-
this.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
this.directories = identity.directories || 'verified';
|
|
86
|
+
this.storage = storage_1.StorageFactory.create({
|
|
87
|
+
storage: options.storage,
|
|
88
|
+
customPath: options.persistencePath,
|
|
89
|
+
memoryKey: options.memoryKey || this.did,
|
|
90
|
+
encryptionPassword: options.encryptionPassword
|
|
91
|
+
});
|
|
92
|
+
this.transport = transport_1.TransportFactory.create({
|
|
93
|
+
transport: options.transport
|
|
94
|
+
});
|
|
95
|
+
this.precomputed = {
|
|
96
|
+
did: this.did,
|
|
97
|
+
publicKey: this.publicKey,
|
|
98
|
+
didBytes: new TextEncoder().encode(this.did),
|
|
99
|
+
signatureCache: new Map()
|
|
100
|
+
};
|
|
101
|
+
if (options.storage !== 'memory') {
|
|
102
|
+
this.rotationManager = new rotation_1.KeyRotationManager(identity, this.transport, {});
|
|
103
|
+
}
|
|
100
104
|
if (this.enableNonceTracking) {
|
|
101
105
|
this.startNonceCleanup();
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Initialize MCP Identity - the main entry point
|
|
106
|
-
*
|
|
107
|
-
* IMPORTANT: This only makes API calls in these cases:
|
|
108
|
-
* 1. First time ever (no identity exists) - registers with all specified registries
|
|
109
|
-
* 2. Adding new registries to existing identity - only calls the new registries
|
|
110
|
-
*
|
|
111
|
-
* After initial registration, all subsequent calls load from disk with NO API calls.
|
|
112
|
-
*/
|
|
113
108
|
static async init(options) {
|
|
114
|
-
|
|
109
|
+
const logger = options?.logger || (0, logger_1.getLogger)();
|
|
115
110
|
if (globalIdentity) {
|
|
116
|
-
// Check if we need to sync to new registries
|
|
117
|
-
if (options?.registries) {
|
|
118
|
-
const requestedRegistries = (0, registry_1.resolveRegistries)(options.registries);
|
|
119
|
-
await globalIdentity.syncToRegistries(requestedRegistries);
|
|
120
|
-
}
|
|
121
111
|
return globalIdentity;
|
|
122
112
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
113
|
+
const storage = storage_1.StorageFactory.create({
|
|
114
|
+
storage: options?.storage,
|
|
115
|
+
customPath: options?.persistencePath,
|
|
116
|
+
memoryKey: options?.memoryKey,
|
|
117
|
+
encryptionPassword: options?.encryptionPassword
|
|
118
|
+
});
|
|
119
|
+
let identity = await storage.load();
|
|
126
120
|
if (identity) {
|
|
127
|
-
|
|
121
|
+
logger.info('Loaded existing identity:', identity.did);
|
|
128
122
|
globalIdentity = new MCPIdentity(identity, options);
|
|
129
|
-
// Sync to any new registries specified in options
|
|
130
|
-
if (options?.registries) {
|
|
131
|
-
const requestedRegistries = (0, registry_1.resolveRegistries)(options.registries);
|
|
132
|
-
await globalIdentity.syncToRegistries(requestedRegistries);
|
|
133
|
-
}
|
|
134
123
|
return globalIdentity;
|
|
135
124
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
primaryRegistry,
|
|
144
|
-
...registriesToUse.filter(r => r !== primaryRegistry)
|
|
145
|
-
];
|
|
146
|
-
console.log('[MCP-I] Publishing to registries:', orderedRegistries.join(', '));
|
|
147
|
-
// Create DID at primary registry (KnowThat by default)
|
|
148
|
-
const primaryAdapter = registry_1.RegistryFactory.getAdapter(primaryRegistry);
|
|
149
|
-
if (!primaryAdapter || primaryAdapter.type !== 'primary') {
|
|
150
|
-
throw new Error(`Primary registry ${primaryRegistry} not available or not a primary registry`);
|
|
151
|
-
}
|
|
152
|
-
const publishData = {
|
|
153
|
-
did: '', // Will be filled after registration
|
|
125
|
+
logger.info('No existing identity found, creating new identity...');
|
|
126
|
+
const transport = transport_1.TransportFactory.create({
|
|
127
|
+
transport: options?.transport
|
|
128
|
+
});
|
|
129
|
+
const apiEndpoint = options?.apiEndpoint || 'https://knowthat.ai';
|
|
130
|
+
logger.info('Registering with knowthat.ai...');
|
|
131
|
+
const registrationData = {
|
|
154
132
|
name: options?.name || process.env.MCP_SERVER_NAME || 'Unnamed MCP Server',
|
|
155
133
|
description: options?.description,
|
|
156
134
|
repository: options?.repository,
|
|
157
|
-
|
|
158
|
-
|
|
135
|
+
directories: options?.directories,
|
|
136
|
+
isDraft: options?.mode === 'development'
|
|
159
137
|
};
|
|
160
|
-
|
|
161
|
-
console.log('[MCP-I] Generating cryptographic keys...');
|
|
138
|
+
logger.info('Generating cryptographic keys...');
|
|
162
139
|
const keyPair = await crypto.generateKeyPair();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (!primaryResult.success) {
|
|
168
|
-
throw new Error(`Failed to register with primary registry: ${primaryResult.error}`);
|
|
169
|
-
}
|
|
170
|
-
// Get the full registration response (this is a bit hacky but maintains compatibility)
|
|
171
|
-
const response = await autoRegister({
|
|
172
|
-
name: publishData.name,
|
|
173
|
-
description: publishData.description,
|
|
174
|
-
repository: publishData.repository,
|
|
175
|
-
apiEndpoint: primaryRegistry === 'knowthat' ? 'https://knowthat.ai' : options?.apiEndpoint || 'https://knowthat.ai'
|
|
140
|
+
const response = await autoRegister(transport, {
|
|
141
|
+
...registrationData,
|
|
142
|
+
apiEndpoint,
|
|
143
|
+
directories: options?.directories
|
|
176
144
|
});
|
|
177
|
-
// Create persisted identity
|
|
178
145
|
identity = {
|
|
179
146
|
did: response.did,
|
|
180
147
|
publicKey: keyPair.publicKey,
|
|
@@ -182,144 +149,113 @@ class MCPIdentity {
|
|
|
182
149
|
agentId: response.agent.id,
|
|
183
150
|
agentSlug: response.agent.slug,
|
|
184
151
|
registeredAt: new Date().toISOString(),
|
|
185
|
-
|
|
186
|
-
registries: [{
|
|
187
|
-
name: primaryRegistry,
|
|
188
|
-
status: 'active',
|
|
189
|
-
type: 'primary',
|
|
190
|
-
registeredAt: new Date().toISOString(),
|
|
191
|
-
registryAgentId: response.agent.id
|
|
192
|
-
}]
|
|
152
|
+
directories: options?.directories || 'verified'
|
|
193
153
|
};
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
154
|
+
await storage.save(identity);
|
|
155
|
+
logger.info('✅ Success! Your agent has been registered.');
|
|
156
|
+
logger.info(`DID: ${response.did}`);
|
|
157
|
+
logger.info(`Profile: ${response.agent.url}`);
|
|
158
|
+
if (response.agent.claimUrl) {
|
|
159
|
+
logger.info(`Claim your agent: ${response.agent.claimUrl}`);
|
|
160
|
+
}
|
|
161
|
+
if (options?.directories && options.directories !== 'none') {
|
|
162
|
+
const dirMessage = options.directories === 'verified'
|
|
163
|
+
? 'Your agent will be submitted to all verified directories'
|
|
164
|
+
: `Your agent will be submitted to: ${options.directories.join(', ')}`;
|
|
165
|
+
logger.info(dirMessage);
|
|
205
166
|
}
|
|
167
|
+
globalIdentity = new MCPIdentity(identity, options);
|
|
206
168
|
return globalIdentity;
|
|
207
169
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
did: this.did,
|
|
217
|
-
name: this.extractAgentName(),
|
|
218
|
-
description: '', // Would need to store this
|
|
219
|
-
repository: '', // Would need to store this
|
|
220
|
-
publicKey: this.publicKey,
|
|
221
|
-
agentId: this.extractAgentId(),
|
|
222
|
-
agentSlug: this.extractAgentSlug(),
|
|
223
|
-
metadata: {}
|
|
224
|
-
};
|
|
225
|
-
for (const registryName of registryNames) {
|
|
226
|
-
// Skip if already registered
|
|
227
|
-
if (this.registries.some(r => r.name === registryName && r.status === 'active')) {
|
|
228
|
-
continue;
|
|
170
|
+
async enableAutoRotation(policy) {
|
|
171
|
+
if (!this.rotationManager) {
|
|
172
|
+
throw new Error('Key rotation not available in memory storage mode');
|
|
173
|
+
}
|
|
174
|
+
this.rotationManager.setupAutoRotation((result) => {
|
|
175
|
+
if (result.success) {
|
|
176
|
+
this.logger.info('Keys rotated successfully');
|
|
177
|
+
this.persistIdentity();
|
|
229
178
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
console.warn(`[MCP-I] Registry ${registryName} not available, skipping`);
|
|
233
|
-
continue;
|
|
179
|
+
else {
|
|
180
|
+
this.logger.error('Key rotation failed:', result.error);
|
|
234
181
|
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async rotateKeys(reason) {
|
|
185
|
+
if (!this.rotationManager) {
|
|
186
|
+
throw new Error('Key rotation not available in memory storage mode');
|
|
187
|
+
}
|
|
188
|
+
const result = await this.rotationManager.rotateKeys(reason);
|
|
189
|
+
if (result.success) {
|
|
190
|
+
await this.persistIdentity();
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
checkKeyHealth() {
|
|
195
|
+
if (!this.rotationManager) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
return this.rotationManager.checkKeyHealth();
|
|
199
|
+
}
|
|
200
|
+
async getPrivateKey() {
|
|
201
|
+
if (this.decryptedPrivateKey) {
|
|
202
|
+
return this.decryptedPrivateKey;
|
|
203
|
+
}
|
|
204
|
+
if (this.encryptionPassword && this.privateKey.startsWith('enc:')) {
|
|
235
205
|
try {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// Update registry status
|
|
240
|
-
const existingIndex = this.registries.findIndex(r => r.name === registryName);
|
|
241
|
-
const newStatus = {
|
|
242
|
-
name: registryName,
|
|
243
|
-
status: 'active',
|
|
244
|
-
type: adapter.type,
|
|
245
|
-
registeredAt: new Date().toISOString(),
|
|
246
|
-
registryAgentId: result.registryAgentId
|
|
247
|
-
};
|
|
248
|
-
if (existingIndex >= 0) {
|
|
249
|
-
this.registries[existingIndex] = newStatus;
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
this.registries.push(newStatus);
|
|
253
|
-
}
|
|
254
|
-
console.log(`[MCP-I] ✅ Published to ${registryName}: ${result.profileUrl || 'success'}`);
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
console.error(`[MCP-I] ❌ Failed to publish to ${registryName}: ${result.error}`);
|
|
258
|
-
// Update registry status with error
|
|
259
|
-
this.registries.push({
|
|
260
|
-
name: registryName,
|
|
261
|
-
status: 'failed',
|
|
262
|
-
type: adapter.type,
|
|
263
|
-
error: result.error,
|
|
264
|
-
lastSyncAt: new Date().toISOString()
|
|
265
|
-
});
|
|
266
|
-
}
|
|
206
|
+
this.decryptedPrivateKey = await crypto.decrypt(this.privateKey, this.encryptionPassword);
|
|
207
|
+
this.logger.debug('Private key decrypted successfully');
|
|
208
|
+
return this.decryptedPrivateKey;
|
|
267
209
|
}
|
|
268
210
|
catch (error) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
name: registryName,
|
|
272
|
-
status: 'failed',
|
|
273
|
-
type: 'secondary',
|
|
274
|
-
error: error.message,
|
|
275
|
-
lastSyncAt: new Date().toISOString()
|
|
276
|
-
});
|
|
211
|
+
this.logger.error('Failed to decrypt private key:', error);
|
|
212
|
+
throw new Error('Invalid encryption password');
|
|
277
213
|
}
|
|
278
214
|
}
|
|
279
|
-
|
|
280
|
-
await this.persistRegistryStatus();
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Add a new registry
|
|
284
|
-
*/
|
|
285
|
-
async addRegistry(registryName) {
|
|
286
|
-
await this.syncToRegistries([registryName]);
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Add multiple registries
|
|
290
|
-
*/
|
|
291
|
-
async addRegistries(registryNames) {
|
|
292
|
-
await this.syncToRegistries(registryNames);
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Get current registry status
|
|
296
|
-
*/
|
|
297
|
-
getRegistryStatus() {
|
|
298
|
-
return [...this.registries];
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Check if registered with a specific registry
|
|
302
|
-
*/
|
|
303
|
-
isRegisteredWith(registryName) {
|
|
304
|
-
return this.registries.some(r => r.name === registryName && r.status === 'active');
|
|
215
|
+
return this.privateKey;
|
|
305
216
|
}
|
|
306
|
-
/**
|
|
307
|
-
* Sign a message with the agent's private key using Ed25519
|
|
308
|
-
*/
|
|
309
217
|
async sign(message) {
|
|
310
|
-
|
|
218
|
+
const messageStr = typeof message === 'string' ? message : message.toString('base64');
|
|
219
|
+
const cached = this.precomputed.signatureCache.get(messageStr);
|
|
220
|
+
if (cached) {
|
|
221
|
+
return cached;
|
|
222
|
+
}
|
|
223
|
+
const privateKey = await this.getPrivateKey();
|
|
224
|
+
const signature = await crypto.sign(message, privateKey);
|
|
225
|
+
if (this.precomputed.signatureCache.size > 100) {
|
|
226
|
+
const firstKey = this.precomputed.signatureCache.keys().next().value;
|
|
227
|
+
if (firstKey) {
|
|
228
|
+
this.precomputed.signatureCache.delete(firstKey);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
this.precomputed.signatureCache.set(messageStr, signature);
|
|
232
|
+
if (this.rotationManager) {
|
|
233
|
+
this.rotationManager.incrementSignatureCount();
|
|
234
|
+
}
|
|
235
|
+
return signature;
|
|
236
|
+
}
|
|
237
|
+
async requestEditAccess() {
|
|
238
|
+
const timestamp = Date.now();
|
|
239
|
+
const message = `edit-request:${this.did}:knowthat.ai:${timestamp}`;
|
|
240
|
+
const signature = await this.sign(message);
|
|
241
|
+
const baseUrl = 'https://knowthat.ai';
|
|
242
|
+
const editUrl = new URL(`${baseUrl}/agents/edit`);
|
|
243
|
+
editUrl.searchParams.set('did', this.did);
|
|
244
|
+
editUrl.searchParams.set('timestamp', timestamp.toString());
|
|
245
|
+
editUrl.searchParams.set('signature', signature);
|
|
246
|
+
const claimUrl = new URL(`${baseUrl}/agents/claim`);
|
|
247
|
+
claimUrl.searchParams.set('did', this.did);
|
|
248
|
+
claimUrl.searchParams.set('timestamp', timestamp.toString());
|
|
249
|
+
claimUrl.searchParams.set('signature', signature);
|
|
250
|
+
return {
|
|
251
|
+
editUrl: editUrl.toString(),
|
|
252
|
+
claimUrl: claimUrl.toString()
|
|
253
|
+
};
|
|
311
254
|
}
|
|
312
|
-
/**
|
|
313
|
-
* Verify a signature against a public key
|
|
314
|
-
*/
|
|
315
255
|
async verify(message, signature, publicKey) {
|
|
316
256
|
return crypto.verify(message, signature, publicKey || this.publicKey);
|
|
317
257
|
}
|
|
318
|
-
/**
|
|
319
|
-
* Respond to an MCP-I challenge
|
|
320
|
-
*/
|
|
321
258
|
async respondToChallenge(challenge) {
|
|
322
|
-
// Validate timestamp to prevent replay attacks
|
|
323
259
|
const now = Date.now();
|
|
324
260
|
const challengeAge = now - challenge.timestamp;
|
|
325
261
|
if (challengeAge > this.timestampTolerance) {
|
|
@@ -328,14 +264,12 @@ class MCPIdentity {
|
|
|
328
264
|
if (challengeAge < 0) {
|
|
329
265
|
throw new Error('Challenge timestamp is in the future');
|
|
330
266
|
}
|
|
331
|
-
// Check for nonce reuse if tracking is enabled
|
|
332
267
|
if (this.enableNonceTracking) {
|
|
333
268
|
if (this.usedNonces.has(challenge.nonce)) {
|
|
334
269
|
throw new Error('Nonce already used');
|
|
335
270
|
}
|
|
336
271
|
this.usedNonces.add(challenge.nonce);
|
|
337
272
|
}
|
|
338
|
-
// Create the message to sign
|
|
339
273
|
const messageComponents = [
|
|
340
274
|
challenge.nonce,
|
|
341
275
|
challenge.timestamp.toString(),
|
|
@@ -344,9 +278,7 @@ class MCPIdentity {
|
|
|
344
278
|
(challenge.scope || []).join(',')
|
|
345
279
|
];
|
|
346
280
|
const message = messageComponents.join(':');
|
|
347
|
-
// Sign the challenge
|
|
348
281
|
const signature = await this.sign(message);
|
|
349
|
-
// Return the response
|
|
350
282
|
return {
|
|
351
283
|
did: this.did,
|
|
352
284
|
signature,
|
|
@@ -355,43 +287,29 @@ class MCPIdentity {
|
|
|
355
287
|
publicKey: this.publicKey
|
|
356
288
|
};
|
|
357
289
|
}
|
|
358
|
-
/**
|
|
359
|
-
* Get MCP-I capabilities for advertisement
|
|
360
|
-
*/
|
|
361
290
|
getCapabilities() {
|
|
362
|
-
const activeRegistries = this.registries.filter(r => r.status === 'active');
|
|
363
|
-
const secondaryRegistries = activeRegistries
|
|
364
|
-
.filter(r => r.type === 'secondary')
|
|
365
|
-
.map(r => r.name);
|
|
366
291
|
return {
|
|
367
292
|
version: '1.0',
|
|
368
293
|
did: this.did,
|
|
369
294
|
publicKey: this.publicKey,
|
|
370
|
-
conformanceLevel: 2,
|
|
295
|
+
conformanceLevel: 2,
|
|
371
296
|
handshakeSupported: true,
|
|
372
297
|
handshakeEndpoint: '/_mcp-i/handshake',
|
|
373
|
-
verificationEndpoint: `https
|
|
374
|
-
|
|
375
|
-
primary: this.didHost,
|
|
376
|
-
secondary: secondaryRegistries
|
|
377
|
-
}
|
|
298
|
+
verificationEndpoint: `https://knowthat.ai/api/agents/${this.did}/verify`,
|
|
299
|
+
registry: 'knowthat.ai'
|
|
378
300
|
};
|
|
379
301
|
}
|
|
380
|
-
/**
|
|
381
|
-
* Sign an MCP response with identity metadata
|
|
382
|
-
*/
|
|
383
302
|
async signResponse(response) {
|
|
384
303
|
const timestamp = new Date().toISOString();
|
|
385
304
|
const responseWithIdentity = {
|
|
386
305
|
...response,
|
|
387
306
|
_mcp_identity: {
|
|
388
307
|
did: this.did,
|
|
389
|
-
signature: '',
|
|
308
|
+
signature: '',
|
|
390
309
|
timestamp,
|
|
391
310
|
conformanceLevel: 2
|
|
392
311
|
}
|
|
393
312
|
};
|
|
394
|
-
// Sign the response content (excluding the signature field)
|
|
395
313
|
const contentToSign = JSON.stringify({
|
|
396
314
|
...response,
|
|
397
315
|
_mcp_identity: {
|
|
@@ -403,227 +321,142 @@ class MCPIdentity {
|
|
|
403
321
|
responseWithIdentity._mcp_identity.signature = await this.sign(contentToSign);
|
|
404
322
|
return responseWithIdentity;
|
|
405
323
|
}
|
|
406
|
-
/**
|
|
407
|
-
* Generate a new nonce for challenges
|
|
408
|
-
*/
|
|
409
324
|
static generateNonce() {
|
|
410
|
-
return crypto.
|
|
325
|
+
return crypto.generateNonceSync();
|
|
411
326
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
* Returns a signed URL for editing the agent on the registry
|
|
415
|
-
*/
|
|
416
|
-
async requestEditAccess(registryName) {
|
|
417
|
-
const registry = registryName || this.didHost;
|
|
418
|
-
const timestamp = Date.now();
|
|
419
|
-
const message = `edit-request:${this.did}:${registry}:${timestamp}`;
|
|
420
|
-
const signature = await this.sign(message);
|
|
421
|
-
// Construct edit URL with proof of ownership
|
|
422
|
-
const baseUrl = registry === 'knowthat'
|
|
423
|
-
? 'https://knowthat.ai'
|
|
424
|
-
: `https://${registry}`;
|
|
425
|
-
const editUrl = new URL(`${baseUrl}/agents/edit`);
|
|
426
|
-
editUrl.searchParams.set('did', this.did);
|
|
427
|
-
editUrl.searchParams.set('timestamp', timestamp.toString());
|
|
428
|
-
editUrl.searchParams.set('signature', signature);
|
|
429
|
-
return editUrl.toString();
|
|
327
|
+
getDirectories() {
|
|
328
|
+
return this.directories;
|
|
430
329
|
}
|
|
431
|
-
/**
|
|
432
|
-
* Clean up old nonces periodically to prevent memory leaks
|
|
433
|
-
*/
|
|
434
330
|
startNonceCleanup() {
|
|
435
|
-
// Clean up nonces older than 2x the timestamp tolerance
|
|
436
331
|
this.nonceCleanupInterval = setInterval(() => {
|
|
437
|
-
// In a production system, you'd track nonce timestamps
|
|
438
|
-
// For now, we'll clear all nonces periodically
|
|
439
332
|
if (this.usedNonces.size > 10000) {
|
|
440
333
|
this.usedNonces.clear();
|
|
441
334
|
}
|
|
442
335
|
}, this.timestampTolerance * 2);
|
|
443
336
|
}
|
|
444
|
-
/**
|
|
445
|
-
* Clean up resources
|
|
446
|
-
*/
|
|
447
337
|
destroy() {
|
|
448
338
|
if (this.nonceCleanupInterval) {
|
|
449
339
|
clearInterval(this.nonceCleanupInterval);
|
|
450
340
|
}
|
|
451
341
|
this.usedNonces.clear();
|
|
342
|
+
this.precomputed.signatureCache.clear();
|
|
452
343
|
}
|
|
453
|
-
/**
|
|
454
|
-
* Helper to extract agent name from DID
|
|
455
|
-
*/
|
|
456
344
|
extractAgentName() {
|
|
457
|
-
// Try to get from persisted data or environment
|
|
458
345
|
return process.env.MCP_SERVER_NAME || 'Unknown Agent';
|
|
459
346
|
}
|
|
460
|
-
/**
|
|
461
|
-
* Helper to extract agent ID
|
|
462
|
-
*/
|
|
463
347
|
extractAgentId() {
|
|
464
348
|
return process.env.AGENT_ID || '';
|
|
465
349
|
}
|
|
466
|
-
/**
|
|
467
|
-
* Helper to extract agent slug
|
|
468
|
-
*/
|
|
469
350
|
extractAgentSlug() {
|
|
470
351
|
const parts = this.did.split(':');
|
|
471
352
|
return parts[parts.length - 1];
|
|
472
353
|
}
|
|
473
|
-
|
|
474
|
-
* Persist updated registry status
|
|
475
|
-
*/
|
|
476
|
-
async persistRegistryStatus() {
|
|
354
|
+
async persistIdentity() {
|
|
477
355
|
try {
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
356
|
+
const identity = {
|
|
357
|
+
did: this.did,
|
|
358
|
+
publicKey: this.publicKey,
|
|
359
|
+
privateKey: this.privateKey,
|
|
360
|
+
agentId: this.extractAgentId(),
|
|
361
|
+
agentSlug: this.extractAgentSlug(),
|
|
362
|
+
registeredAt: new Date().toISOString(),
|
|
363
|
+
directories: this.directories
|
|
364
|
+
};
|
|
365
|
+
await this.storage.save(identity);
|
|
482
366
|
}
|
|
483
367
|
catch (error) {
|
|
484
|
-
|
|
368
|
+
this.logger.error('Failed to persist identity:', error);
|
|
485
369
|
}
|
|
486
370
|
}
|
|
487
371
|
}
|
|
488
372
|
exports.MCPIdentity = MCPIdentity;
|
|
489
|
-
/**
|
|
490
|
-
* Enable MCP Identity for any MCP server
|
|
491
|
-
* This is the main integration point that patches the MCP Server
|
|
492
|
-
*/
|
|
493
373
|
async function enableMCPIdentity(options) {
|
|
494
374
|
const identity = await MCPIdentity.init(options);
|
|
495
|
-
// Try to patch MCP Server if available
|
|
496
375
|
try {
|
|
497
376
|
patchMCPServer(identity);
|
|
498
377
|
}
|
|
499
378
|
catch (error) {
|
|
500
|
-
|
|
379
|
+
const logger = (0, logger_1.getLogger)();
|
|
380
|
+
logger.debug('MCP Server not found, identity initialized for manual use');
|
|
501
381
|
}
|
|
502
382
|
return identity;
|
|
503
383
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
// Try to import the MCP SDK
|
|
510
|
-
const MCPModule = require('@modelcontextprotocol/sdk/server/index.js');
|
|
511
|
-
const OriginalServer = MCPModule.Server;
|
|
512
|
-
if (!OriginalServer) {
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
// Store original methods
|
|
516
|
-
const originalSetRequestHandler = OriginalServer.prototype.setRequestHandler;
|
|
517
|
-
const originalConnect = OriginalServer.prototype.connect;
|
|
518
|
-
// Patch setRequestHandler to wrap all responses
|
|
519
|
-
OriginalServer.prototype.setRequestHandler = function (method, handler) {
|
|
384
|
+
function createMCPMiddleware(identity) {
|
|
385
|
+
return (server) => {
|
|
386
|
+
const originalSetRequestHandler = server.setRequestHandler.bind(server);
|
|
387
|
+
const originalConnect = server.connect.bind(server);
|
|
388
|
+
server.setRequestHandler = function (method, handler) {
|
|
520
389
|
const wrappedHandler = async (...args) => {
|
|
521
390
|
const result = await handler(...args);
|
|
522
|
-
// If result has content, sign it
|
|
523
391
|
if (result && typeof result === 'object' && 'content' in result) {
|
|
524
|
-
|
|
525
|
-
return signed;
|
|
392
|
+
return await identity.signResponse(result);
|
|
526
393
|
}
|
|
527
394
|
return result;
|
|
528
395
|
};
|
|
529
|
-
return originalSetRequestHandler
|
|
396
|
+
return originalSetRequestHandler(method, wrappedHandler);
|
|
530
397
|
};
|
|
531
|
-
|
|
532
|
-
OriginalServer.prototype.connect = async function (transport) {
|
|
533
|
-
// Add MCP-I capabilities to server info
|
|
398
|
+
server.connect = async function (transport) {
|
|
534
399
|
if (this.serverInfo && this.serverInfo.capabilities) {
|
|
535
400
|
this.serverInfo.capabilities['mcp-i'] = identity.getCapabilities();
|
|
536
401
|
}
|
|
537
|
-
// Set up MCP-I handshake handler
|
|
538
402
|
this.setRequestHandler('mcp-i/challenge', async (request) => {
|
|
539
403
|
return identity.respondToChallenge(request.params);
|
|
540
404
|
});
|
|
541
|
-
|
|
542
|
-
return originalConnect.call(this, transport);
|
|
405
|
+
return originalConnect(transport);
|
|
543
406
|
};
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
catch (error) {
|
|
547
|
-
// MCP SDK not available, that's okay
|
|
548
|
-
}
|
|
407
|
+
};
|
|
549
408
|
}
|
|
550
|
-
|
|
551
|
-
async function loadIdentity(customPath) {
|
|
552
|
-
// Check environment variables first (already loaded by process)
|
|
553
|
-
if (process.env.AGENT_DID && process.env.AGENT_PUBLIC_KEY && process.env.AGENT_PRIVATE_KEY) {
|
|
554
|
-
const identity = {
|
|
555
|
-
did: process.env.AGENT_DID,
|
|
556
|
-
publicKey: process.env.AGENT_PUBLIC_KEY,
|
|
557
|
-
privateKey: process.env.AGENT_PRIVATE_KEY,
|
|
558
|
-
agentId: process.env.AGENT_ID || '',
|
|
559
|
-
agentSlug: process.env.AGENT_SLUG || '',
|
|
560
|
-
registeredAt: new Date().toISOString(),
|
|
561
|
-
didHost: process.env.AGENT_DID_HOST || 'knowthat'
|
|
562
|
-
};
|
|
563
|
-
// Try to load registry status from file
|
|
564
|
-
try {
|
|
565
|
-
const filePath = customPath || path.join(process.cwd(), '.mcp-identity.json');
|
|
566
|
-
if (fs.existsSync(filePath)) {
|
|
567
|
-
const fileContent = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
568
|
-
if (fileContent.registries) {
|
|
569
|
-
identity.registries = fileContent.registries;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
catch {
|
|
574
|
-
// Ignore errors
|
|
575
|
-
}
|
|
576
|
-
return identity;
|
|
577
|
-
}
|
|
578
|
-
// Check JSON file (most reliable for programmatic access)
|
|
579
|
-
const filePath = customPath || path.join(process.cwd(), '.mcp-identity.json');
|
|
409
|
+
function patchMCPServer(identity) {
|
|
580
410
|
try {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
411
|
+
const MCPModule = require('@modelcontextprotocol/sdk/server/index.js');
|
|
412
|
+
const OriginalServer = MCPModule.Server;
|
|
413
|
+
if (!OriginalServer) {
|
|
414
|
+
return;
|
|
584
415
|
}
|
|
416
|
+
const middleware = createMCPMiddleware(identity);
|
|
417
|
+
const OriginalConstructor = OriginalServer;
|
|
418
|
+
MCPModule.Server = function (...args) {
|
|
419
|
+
const instance = new OriginalConstructor(...args);
|
|
420
|
+
middleware(instance, identity);
|
|
421
|
+
return instance;
|
|
422
|
+
};
|
|
423
|
+
Object.setPrototypeOf(MCPModule.Server, OriginalConstructor);
|
|
424
|
+
Object.setPrototypeOf(MCPModule.Server.prototype, OriginalConstructor.prototype);
|
|
425
|
+
const logger = (0, logger_1.getLogger)();
|
|
426
|
+
logger.info('✨ MCP Server patched - all responses will be automatically signed');
|
|
585
427
|
}
|
|
586
|
-
catch {
|
|
587
|
-
// Ignore errors
|
|
428
|
+
catch (error) {
|
|
588
429
|
}
|
|
589
|
-
return null;
|
|
590
|
-
}
|
|
591
|
-
async function saveIdentity(identity, customPath) {
|
|
592
|
-
// Save as JSON for programmatic access
|
|
593
|
-
const filePath = customPath || path.join(process.cwd(), '.mcp-identity.json');
|
|
594
|
-
fs.writeFileSync(filePath, JSON.stringify(identity, null, 2));
|
|
595
|
-
console.log('[MCP-I] Identity saved to .mcp-identity.json');
|
|
596
430
|
}
|
|
597
|
-
async function autoRegister(options) {
|
|
431
|
+
async function autoRegister(transport, options) {
|
|
598
432
|
try {
|
|
599
|
-
const response = await
|
|
433
|
+
const response = await transport.post(`${options.apiEndpoint}/api/agents/auto-register`, {
|
|
600
434
|
metadata: {
|
|
601
435
|
name: options.name,
|
|
602
436
|
description: options.description,
|
|
603
437
|
repository: options.repository,
|
|
604
|
-
version: '1.0.0'
|
|
438
|
+
version: '1.0.0',
|
|
439
|
+
isDraft: options.isDraft,
|
|
440
|
+
directories: options.directories || 'verified'
|
|
605
441
|
},
|
|
606
442
|
clientInfo: {
|
|
607
|
-
sdkVersion: '0.
|
|
443
|
+
sdkVersion: '0.3.0',
|
|
608
444
|
language: 'typescript',
|
|
609
|
-
platform: 'node'
|
|
445
|
+
platform: 'node'
|
|
610
446
|
}
|
|
611
447
|
}, {
|
|
612
448
|
timeout: 30000,
|
|
613
449
|
headers: {
|
|
614
450
|
'Content-Type': 'application/json',
|
|
615
|
-
'User-Agent': '@kya-os/mcp-i/0.
|
|
451
|
+
'User-Agent': '@kya-os/mcp-i/0.3.0'
|
|
616
452
|
}
|
|
617
453
|
});
|
|
618
454
|
return response.data;
|
|
619
455
|
}
|
|
620
456
|
catch (error) {
|
|
621
|
-
if (error.
|
|
457
|
+
if (error.message?.includes('429')) {
|
|
622
458
|
throw new Error('Rate limit exceeded. Please try again later.');
|
|
623
459
|
}
|
|
624
|
-
throw new Error(error.
|
|
625
|
-
error.message ||
|
|
626
|
-
'Failed to auto-register agent');
|
|
460
|
+
throw new Error(error.message || 'Failed to auto-register agent');
|
|
627
461
|
}
|
|
628
462
|
}
|
|
629
|
-
//# sourceMappingURL=index.js.map
|