miadi 1.0.14
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/.env.example +28 -0
- package/ARCHITECTURE.md +290 -0
- package/CLAUDE.md +269 -0
- package/GEMINI.md +80 -0
- package/MCP_CONNECTOR_READY.md +219 -0
- package/MCP_LEARNING_NOTES.md +178 -0
- package/MCP_REBUILD_PLAN.md +159 -0
- package/MCP_REMOTE_SERVER_SPEC.md +373 -0
- package/MIA.md +344 -0
- package/MIETTE.md +195 -0
- package/README.md +264 -0
- package/REMOTE_MCP_TRANSFORMATION_GUIDE.md +384 -0
- package/STATUS.md +191 -0
- package/TOOL_SELECTION_PLAN.md +340 -0
- package/WAKE_UP_SUMMARY.md +102 -0
- package/__PUBLISH.sh +1 -0
- package/book/_/ledgers/ledger_miadi_mcp_analysis_250730.md +0 -0
- package/conversations/2507301433.claude.issue.11.2025-07-30-this-mcp-is-not-working-another-instance-of-yours.txt +756 -0
- package/conversations/2507301601.cursor.reverse_engineer_mcp_service_for.md +808 -0
- package/conversations/2508050125.llmcon.claude.MIADI_TOOLS-implement-what-is-in-toolselectionplanmd.txt +1235 -0
- package/conversations/2508051939.llmcon.claude.issue-14.TransitionToPlanningIT.implement-what-is-in-toolselectionplanmd.txt +1424 -0
- package/conversations/2508082352.llmcon.claude.MCP-Remote-Take-II.txt +1658 -0
- package/dist/index-remote.js +54736 -0
- package/dist/index.js +32363 -0
- package/mcp.sample.json +14 -0
- package/openapi.yml +2161 -0
- package/package.json +56 -0
- package/research/MCP_Research_Perplexity_2508060045.md +410 -0
- package/samples/README.md +2 -0
- package/scripts/ngrokserve.sh +6 -0
- package/scripts/start-remote.sh +141 -0
- package/scripts/start-with-ngrok.sh +140 -0
- package/src/api-client.ts +254 -0
- package/src/index-remote.ts +406 -0
- package/src/index-simple.ts +232 -0
- package/src/index.ts +510 -0
- package/src/tool-registry.ts +223 -0
- package/src/tools/ai-tools.ts +69 -0
- package/src/tools/capability-tools.ts +79 -0
- package/src/tools/forge-tools.ts +51 -0
- package/src/tools/memory-tools.ts +137 -0
- package/src/tools/session-tools.ts +135 -0
- package/src/tools/workflow-tools.ts +65 -0
- package/src/types.ts +291 -0
- package/src/utils.ts +279 -0
- package/tests/quick-test.sh +116 -0
- package/tests/run-all-tests.sh +167 -0
- package/tests/test-agent-capabilities.sh +364 -0
- package/tests/test-connectivity.sh +90 -0
- package/tests/test-memory-operations.sh +236 -0
- package/tests/test-session-management.sh +320 -0
- package/tests/test-tool-discovery.sh +151 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { miadiApi } from '../api-client';
|
|
2
|
+
import {
|
|
3
|
+
AgentRegisterRequest,
|
|
4
|
+
AgentRegisterResponse,
|
|
5
|
+
AgentEventsResponse,
|
|
6
|
+
WorkflowHowtoResponse
|
|
7
|
+
} from '../types';
|
|
8
|
+
|
|
9
|
+
export const workflowTools = {
|
|
10
|
+
/**
|
|
11
|
+
* Register an agent for GitHub event notifications
|
|
12
|
+
*/
|
|
13
|
+
registerAgent: async (
|
|
14
|
+
agentId: string,
|
|
15
|
+
eventTypes: string[],
|
|
16
|
+
callbackUrl?: string
|
|
17
|
+
): Promise<AgentRegisterResponse> => {
|
|
18
|
+
try {
|
|
19
|
+
if (!agentId || !eventTypes || eventTypes.length === 0) {
|
|
20
|
+
throw new Error('Agent ID and event types are required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const requestBody: AgentRegisterRequest = {
|
|
24
|
+
agentId,
|
|
25
|
+
eventTypes
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (callbackUrl) requestBody.callbackUrl = callbackUrl;
|
|
29
|
+
|
|
30
|
+
return await miadiApi.registerAgent(requestBody);
|
|
31
|
+
} catch (error: any) {
|
|
32
|
+
throw new Error(`Failed to register agent: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check for GitHub events for an agent
|
|
38
|
+
*/
|
|
39
|
+
getAgentEvents: async (agentId: string): Promise<AgentEventsResponse> => {
|
|
40
|
+
try {
|
|
41
|
+
if (!agentId) {
|
|
42
|
+
throw new Error('Agent ID is required');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return await miadiApi.getAgentEvents(agentId);
|
|
46
|
+
} catch (error: any) {
|
|
47
|
+
throw new Error(`Failed to get agent events: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get workflow setup guides and HOWTO information
|
|
53
|
+
*/
|
|
54
|
+
getWorkflowHowto: async (
|
|
55
|
+
topic?: 'github-webhook-setup' | 'agent-registration' | 'agent-event-polling' | 'inter-agent-communication' | 'workflow-management' | 'authentication' | 'troubleshooting'
|
|
56
|
+
): Promise<WorkflowHowtoResponse | any> => {
|
|
57
|
+
try {
|
|
58
|
+
return await miadiApi.getWorkflowHowto(topic);
|
|
59
|
+
} catch (error: any) {
|
|
60
|
+
throw new Error(`Failed to get workflow howto: ${error.message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default workflowTools;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// TypeScript interfaces based on Miadi Three-Pathway Agent System OpenAPI specification
|
|
2
|
+
|
|
3
|
+
export interface ErrorResponse {
|
|
4
|
+
error: string;
|
|
5
|
+
message: string;
|
|
6
|
+
code?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Memory Management Types
|
|
10
|
+
export interface GetMemoryResponse {
|
|
11
|
+
key?: string;
|
|
12
|
+
type?: 'string' | 'hash' | 'list' | 'set' | 'zset';
|
|
13
|
+
value?: any;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
keys?: string[]; // For list=true
|
|
16
|
+
count?: number; // For list=true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StoreMemoryRequest {
|
|
20
|
+
key: string;
|
|
21
|
+
value: string;
|
|
22
|
+
ttl?: number;
|
|
23
|
+
type?: 'string' | 'hash' | 'list';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StoreMemoryResponse {
|
|
27
|
+
key: string;
|
|
28
|
+
value: string;
|
|
29
|
+
ttl?: number;
|
|
30
|
+
timestamp: string;
|
|
31
|
+
message: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ScanResponse {
|
|
35
|
+
keys: string[];
|
|
36
|
+
count: number;
|
|
37
|
+
timestamp: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface GatherMemoryResponse {
|
|
41
|
+
items: Array<{
|
|
42
|
+
key: string;
|
|
43
|
+
value: any;
|
|
44
|
+
}>;
|
|
45
|
+
count: number;
|
|
46
|
+
timestamp: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Session Management Types
|
|
50
|
+
export interface ContextVariable {
|
|
51
|
+
name: string;
|
|
52
|
+
type: 'location' | 'device' | 'activity' | 'time' | 'custom';
|
|
53
|
+
value: any;
|
|
54
|
+
description?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SessionStartRequest {
|
|
58
|
+
persona: string;
|
|
59
|
+
mode: string;
|
|
60
|
+
contextOverrides?: ContextVariable[];
|
|
61
|
+
userId?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface AgentSession {
|
|
65
|
+
id: string;
|
|
66
|
+
userId?: string;
|
|
67
|
+
activePersona: string;
|
|
68
|
+
currentMode: string;
|
|
69
|
+
resolvedCapabilities: ResolvedCapability[];
|
|
70
|
+
contextOverrides?: ContextVariable[];
|
|
71
|
+
startTime: number;
|
|
72
|
+
lastActivity: number;
|
|
73
|
+
metadata?: any;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface SessionStartResponse {
|
|
77
|
+
session: AgentSession;
|
|
78
|
+
availableCapabilities: ResolvedCapability[];
|
|
79
|
+
currentContext: ContextVariable[];
|
|
80
|
+
timestamp: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface SessionCurrentResponse {
|
|
84
|
+
session: AgentSession | null;
|
|
85
|
+
availableCapabilities: ResolvedCapability[];
|
|
86
|
+
currentContext: ContextVariable[];
|
|
87
|
+
timestamp: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SessionSwitchModeRequest {
|
|
91
|
+
sessionId: string;
|
|
92
|
+
newMode: string;
|
|
93
|
+
contextOverrides?: ContextVariable[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface SessionSwitchPersonaRequest {
|
|
97
|
+
sessionId: string;
|
|
98
|
+
newPersona: string;
|
|
99
|
+
preserveMode?: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface SessionListResponse {
|
|
103
|
+
sessions: AgentSession[];
|
|
104
|
+
count: number;
|
|
105
|
+
limit: number;
|
|
106
|
+
offset: number;
|
|
107
|
+
timestamp: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Capability Types
|
|
111
|
+
export interface Capability {
|
|
112
|
+
id: string;
|
|
113
|
+
description: string;
|
|
114
|
+
category?: 'communication' | 'analysis' | 'creation' | 'navigation' | 'wellness' | 'system';
|
|
115
|
+
permissions?: string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface ResolvedCapability extends Capability {
|
|
119
|
+
source: 'persona' | 'mode' | 'global' | 'context';
|
|
120
|
+
priority: number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface CapabilityResolveRequest {
|
|
124
|
+
persona: string;
|
|
125
|
+
mode: string;
|
|
126
|
+
context?: ContextVariable[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface CapabilityResolveResponse {
|
|
130
|
+
capabilities: ResolvedCapability[];
|
|
131
|
+
timestamp: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Agent Info Types
|
|
135
|
+
export interface AgentInfoResponse {
|
|
136
|
+
personas: Array<{
|
|
137
|
+
id: string;
|
|
138
|
+
name: string;
|
|
139
|
+
agentId: string;
|
|
140
|
+
description: string;
|
|
141
|
+
capabilities: string[];
|
|
142
|
+
defaultMode: string;
|
|
143
|
+
}>;
|
|
144
|
+
modes: Array<{
|
|
145
|
+
id: string;
|
|
146
|
+
name: string;
|
|
147
|
+
description: string;
|
|
148
|
+
defaultCapabilities: string[];
|
|
149
|
+
triggers: Array<{
|
|
150
|
+
phrase: string;
|
|
151
|
+
confidence: number;
|
|
152
|
+
autoSwitch: boolean;
|
|
153
|
+
}>;
|
|
154
|
+
}>;
|
|
155
|
+
capabilities: Capability[];
|
|
156
|
+
examples: Record<string, {
|
|
157
|
+
method: string;
|
|
158
|
+
url: string;
|
|
159
|
+
body: any;
|
|
160
|
+
}>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// AI Integration Types
|
|
164
|
+
export interface OpenAIRequest {
|
|
165
|
+
prompt: string;
|
|
166
|
+
modelId?: string;
|
|
167
|
+
maxTokens?: number;
|
|
168
|
+
temperature?: number;
|
|
169
|
+
stream?: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface OpenAIResponse {
|
|
173
|
+
id: string;
|
|
174
|
+
model: string;
|
|
175
|
+
choices: Array<{
|
|
176
|
+
text: string;
|
|
177
|
+
index: number;
|
|
178
|
+
finish_reason: string;
|
|
179
|
+
}>;
|
|
180
|
+
usage: {
|
|
181
|
+
prompt_tokens: number;
|
|
182
|
+
completion_tokens: number;
|
|
183
|
+
total_tokens: number;
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface AIRequest {
|
|
188
|
+
prompt: string;
|
|
189
|
+
modelId?: string;
|
|
190
|
+
maxTokens?: number;
|
|
191
|
+
temperature?: number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface AIResponse {
|
|
195
|
+
id: string;
|
|
196
|
+
model: string;
|
|
197
|
+
choices: Array<{
|
|
198
|
+
text: string;
|
|
199
|
+
index: number;
|
|
200
|
+
finish_reason: string;
|
|
201
|
+
}>;
|
|
202
|
+
usage: {
|
|
203
|
+
prompt_tokens: number;
|
|
204
|
+
completion_tokens: number;
|
|
205
|
+
total_tokens: number;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Workflow Types
|
|
210
|
+
export interface AgentRegisterRequest {
|
|
211
|
+
agentId: string;
|
|
212
|
+
eventTypes: string[];
|
|
213
|
+
callbackUrl?: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface AgentRegisterResponse {
|
|
217
|
+
message: string;
|
|
218
|
+
agentId: string;
|
|
219
|
+
eventTypes: string[];
|
|
220
|
+
timestamp: string;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface AgentEventsResponse {
|
|
224
|
+
agentId: string;
|
|
225
|
+
events: Array<{
|
|
226
|
+
eventId: string;
|
|
227
|
+
type: string;
|
|
228
|
+
payload: any;
|
|
229
|
+
timestamp: string;
|
|
230
|
+
}>;
|
|
231
|
+
count: number;
|
|
232
|
+
timestamp: string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface WorkflowHowtoResponse {
|
|
236
|
+
topic: string;
|
|
237
|
+
guide: {
|
|
238
|
+
title: string;
|
|
239
|
+
steps: string[];
|
|
240
|
+
};
|
|
241
|
+
timestamp: string;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Forge State Types
|
|
245
|
+
export interface ForgeStateRequest {
|
|
246
|
+
state: boolean;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface ForgeStateResponse {
|
|
250
|
+
isOpen: boolean;
|
|
251
|
+
timestamp: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface ForgeStateUpdateResponse {
|
|
255
|
+
isOpen: boolean;
|
|
256
|
+
timestamp: string;
|
|
257
|
+
message: string;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Cue Detection Types
|
|
261
|
+
export interface CueTrigger {
|
|
262
|
+
phrase: string;
|
|
263
|
+
confidence: number;
|
|
264
|
+
autoSwitch: boolean;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface CueDetection {
|
|
268
|
+
targetId: string;
|
|
269
|
+
type: 'mode' | 'persona';
|
|
270
|
+
confidence: number;
|
|
271
|
+
autoSwitch: boolean;
|
|
272
|
+
trigger: CueTrigger;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface CueDetectRequest {
|
|
276
|
+
text: string;
|
|
277
|
+
currentSession?: AgentSession;
|
|
278
|
+
availableModes?: string[];
|
|
279
|
+
availablePersonas?: string[];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface CueDetectResponse {
|
|
283
|
+
detections: CueDetection[];
|
|
284
|
+
suggestedAction?: {
|
|
285
|
+
type: 'mode' | 'persona';
|
|
286
|
+
targetId: string;
|
|
287
|
+
confidence: number;
|
|
288
|
+
autoSwitch: boolean;
|
|
289
|
+
};
|
|
290
|
+
timestamp: string;
|
|
291
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { ErrorResponse } from './types';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Utility functions for the Miadi MCP Server
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Standardized error handler for MCP tools
|
|
12
|
+
*/
|
|
13
|
+
export function handleMCPError(error: any, operation: string): never {
|
|
14
|
+
const message = error.message || error.toString();
|
|
15
|
+
console.error(`MCP Tool Error [${operation}]:`, message);
|
|
16
|
+
throw new Error(`${operation} failed: ${message}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate required parameters for MCP tools
|
|
21
|
+
*/
|
|
22
|
+
export function validateRequired(params: Record<string, any>, requiredFields: string[]): void {
|
|
23
|
+
const missing = requiredFields.filter(field => !params[field]);
|
|
24
|
+
if (missing.length > 0) {
|
|
25
|
+
throw new Error(`Required parameters missing: ${missing.join(', ')}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sanitize input parameters to prevent injection
|
|
31
|
+
*/
|
|
32
|
+
export function sanitizeInput(input: string): string {
|
|
33
|
+
if (typeof input !== 'string') return input;
|
|
34
|
+
|
|
35
|
+
// Basic sanitization - remove potentially dangerous characters
|
|
36
|
+
return input
|
|
37
|
+
.replace(/[<>]/g, '') // Remove HTML tags
|
|
38
|
+
.replace(/['"]/g, '') // Remove quotes
|
|
39
|
+
.trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Format timestamp for consistent output
|
|
44
|
+
*/
|
|
45
|
+
export function formatTimestamp(date?: Date): string {
|
|
46
|
+
return (date || new Date()).toISOString();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if a value is a valid UUID
|
|
51
|
+
*/
|
|
52
|
+
export function isValidUUID(uuid: string): boolean {
|
|
53
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
54
|
+
return uuidRegex.test(uuid);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validate session ID format
|
|
59
|
+
*/
|
|
60
|
+
export function validateSessionId(sessionId: string): void {
|
|
61
|
+
if (!sessionId) {
|
|
62
|
+
throw new Error('Session ID is required');
|
|
63
|
+
}
|
|
64
|
+
if (!isValidUUID(sessionId)) {
|
|
65
|
+
throw new Error('Session ID must be a valid UUID');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Enhanced environment loading that supports:
|
|
71
|
+
* 1. MCP configuration environment variables (highest priority)
|
|
72
|
+
* 2. Current process environment variables
|
|
73
|
+
* 3. $HOME/.env file as fallback (lowest priority)
|
|
74
|
+
*/
|
|
75
|
+
export function loadEnvironmentVariables(): void {
|
|
76
|
+
// First, try to load from $HOME/.env as fallback
|
|
77
|
+
const homeEnvPath = path.join(os.homedir(), '.env');
|
|
78
|
+
|
|
79
|
+
if (fs.existsSync(homeEnvPath)) {
|
|
80
|
+
try {
|
|
81
|
+
const homeEnvContent = fs.readFileSync(homeEnvPath, 'utf8');
|
|
82
|
+
const homeEnvVars = parseEnvFile(homeEnvContent);
|
|
83
|
+
|
|
84
|
+
// Only set variables that don't already exist in process.env
|
|
85
|
+
Object.entries(homeEnvVars).forEach(([key, value]) => {
|
|
86
|
+
if (!process.env[key]) {
|
|
87
|
+
process.env[key] = value;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.error(`✅ Loaded fallback environment variables from ${homeEnvPath}`);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(`⚠️ Failed to load ${homeEnvPath}:`, error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse .env file content into key-value pairs
|
|
100
|
+
*/
|
|
101
|
+
function parseEnvFile(content: string): Record<string, string> {
|
|
102
|
+
const result: Record<string, string> = {};
|
|
103
|
+
|
|
104
|
+
const lines = content.split('\n');
|
|
105
|
+
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
const trimmedLine = line.trim();
|
|
108
|
+
|
|
109
|
+
// Skip empty lines and comments
|
|
110
|
+
if (!trimmedLine || trimmedLine.startsWith('#')) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Parse KEY=VALUE format
|
|
115
|
+
const equalIndex = trimmedLine.indexOf('=');
|
|
116
|
+
if (equalIndex === -1) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const key = trimmedLine.substring(0, equalIndex).trim();
|
|
121
|
+
let value = trimmedLine.substring(equalIndex + 1).trim();
|
|
122
|
+
|
|
123
|
+
// Remove surrounding quotes if present
|
|
124
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
125
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
126
|
+
value = value.slice(1, -1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (key) {
|
|
130
|
+
result[key] = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Parse environment variables with defaults
|
|
139
|
+
* This function now relies on the enhanced environment loading
|
|
140
|
+
*/
|
|
141
|
+
export function getEnvVar(name: string, defaultValue?: string): string {
|
|
142
|
+
const value = process.env[name];
|
|
143
|
+
if (!value && !defaultValue) {
|
|
144
|
+
throw new Error(`Environment variable ${name} is required`);
|
|
145
|
+
}
|
|
146
|
+
return value || defaultValue!;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Safe JSON parsing with error handling
|
|
151
|
+
*/
|
|
152
|
+
export function safeJsonParse(jsonString: string): any {
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(jsonString);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new Error(`Invalid JSON format: ${error}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Format API error response for MCP tools
|
|
162
|
+
*/
|
|
163
|
+
export function formatApiError(error: any): ErrorResponse {
|
|
164
|
+
return {
|
|
165
|
+
error: error.name || 'API Error',
|
|
166
|
+
message: error.message || 'Unknown error occurred',
|
|
167
|
+
code: error.status || error.code || 500
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Validate context variables array
|
|
173
|
+
*/
|
|
174
|
+
export function validateContextVariables(contextOverrides?: any[]): void {
|
|
175
|
+
if (!contextOverrides) return;
|
|
176
|
+
|
|
177
|
+
if (!Array.isArray(contextOverrides)) {
|
|
178
|
+
throw new Error('contextOverrides must be an array');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const validTypes = ['location', 'device', 'activity', 'time', 'custom'];
|
|
182
|
+
|
|
183
|
+
contextOverrides.forEach((context, index) => {
|
|
184
|
+
if (!context.name || !context.type) {
|
|
185
|
+
throw new Error(`Context variable at index ${index} must have 'name' and 'type' properties`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!validTypes.includes(context.type)) {
|
|
189
|
+
throw new Error(`Context variable '${context.name}' has invalid type. Must be one of: ${validTypes.join(', ')}`);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Log tool usage for debugging
|
|
196
|
+
*/
|
|
197
|
+
export function logToolUsage(toolName: string, parameters: any): void {
|
|
198
|
+
const logLevel = process.env.LOG_LEVEL || 'info';
|
|
199
|
+
|
|
200
|
+
if (logLevel === 'debug') {
|
|
201
|
+
console.log(`[MCP Tool] ${toolName}:`, JSON.stringify(parameters, null, 2));
|
|
202
|
+
} else if (logLevel === 'info') {
|
|
203
|
+
console.log(`[MCP Tool] ${toolName} called`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Create consistent response format for MCP tools
|
|
209
|
+
*/
|
|
210
|
+
export function createMCPResponse<T>(data: T, toolName: string): { content: Array<{ type: "text"; text: string }>; _meta?: { tool: string; timestamp: string } } {
|
|
211
|
+
return {
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: "text",
|
|
215
|
+
text: JSON.stringify(data, null, 2)
|
|
216
|
+
}
|
|
217
|
+
],
|
|
218
|
+
_meta: {
|
|
219
|
+
tool: toolName,
|
|
220
|
+
timestamp: formatTimestamp()
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Parse tool selection configuration from environment variables
|
|
227
|
+
*/
|
|
228
|
+
export function parseToolSelection(): import('./tool-registry').ToolRegistryConfig {
|
|
229
|
+
const enabled = process.env.MIADI_TOOLS_ENABLED;
|
|
230
|
+
const disabled = process.env.MIADI_TOOLS_DISABLED;
|
|
231
|
+
const defaultEnabled = process.env.MIADI_TOOLS_DEFAULT_ENABLED !== 'false';
|
|
232
|
+
|
|
233
|
+
const config: import('./tool-registry').ToolRegistryConfig = { defaultEnabled };
|
|
234
|
+
|
|
235
|
+
if (enabled) {
|
|
236
|
+
const items = enabled.split(',').map(s => s.trim()).filter(s => s.length > 0);
|
|
237
|
+
const categories = items.filter(item => !item.startsWith('miadi-') && !item.startsWith('-'));
|
|
238
|
+
const tools = items.filter(item => item.startsWith('miadi-'));
|
|
239
|
+
const excludedTools = items.filter(item => item.startsWith('-')).map(item => item.substring(1));
|
|
240
|
+
|
|
241
|
+
if (categories.length > 0) config.enabledCategories = categories;
|
|
242
|
+
if (tools.length > 0) config.enabledTools = tools;
|
|
243
|
+
if (excludedTools.length > 0) config.disabledTools = excludedTools;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (disabled) {
|
|
247
|
+
const items = disabled.split(',').map(s => s.trim()).filter(s => s.length > 0);
|
|
248
|
+
const categories = items.filter(item => !item.startsWith('miadi-'));
|
|
249
|
+
const tools = items.filter(item => item.startsWith('miadi-'));
|
|
250
|
+
|
|
251
|
+
if (categories.length > 0) config.disabledCategories = categories;
|
|
252
|
+
if (tools.length > 0) {
|
|
253
|
+
config.disabledTools = [...(config.disabledTools || []), ...tools];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return config;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Rate limiting helper (simple in-memory implementation)
|
|
262
|
+
*/
|
|
263
|
+
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
|
|
264
|
+
|
|
265
|
+
export function checkRateLimit(identifier: string, maxRequests: number = 100, windowMs: number = 60000): void {
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
const entry = rateLimitMap.get(identifier);
|
|
268
|
+
|
|
269
|
+
if (!entry || now > entry.resetTime) {
|
|
270
|
+
rateLimitMap.set(identifier, { count: 1, resetTime: now + windowMs });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (entry.count >= maxRequests) {
|
|
275
|
+
throw new Error(`Rate limit exceeded for ${identifier}. Try again later.`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
entry.count++;
|
|
279
|
+
}
|