@topolo/mcp 0.1.1 → 0.1.2
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/dist/index.js +100 -47
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
import {
|
|
7
|
-
CallToolRequestSchema,
|
|
8
|
-
ListToolsRequestSchema
|
|
9
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
5
|
|
|
11
6
|
// node_modules/@topolo/sdk/dist/index.js
|
|
12
7
|
function applyAuthHeaders(headers, credential) {
|
|
@@ -86,6 +81,7 @@ var TopoloClient = class {
|
|
|
86
81
|
requireConfirmForWrites;
|
|
87
82
|
timeoutMs;
|
|
88
83
|
fetchImpl;
|
|
84
|
+
debug;
|
|
89
85
|
constructor(options) {
|
|
90
86
|
if (!options.credential) throw new TopoloAuthError("credential is required");
|
|
91
87
|
if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
|
|
@@ -95,6 +91,14 @@ var TopoloClient = class {
|
|
|
95
91
|
this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
|
|
96
92
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
97
93
|
this.fetchImpl = options.fetch ?? fetch;
|
|
94
|
+
this.debug = options.debug;
|
|
95
|
+
}
|
|
96
|
+
emit(event) {
|
|
97
|
+
if (!this.debug) return;
|
|
98
|
+
try {
|
|
99
|
+
this.debug(event);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
98
102
|
}
|
|
99
103
|
/**
|
|
100
104
|
* Low-level JSON request. Prefer the typed module helpers (identity, crm, ...)
|
|
@@ -121,27 +125,56 @@ var TopoloClient = class {
|
|
|
121
125
|
const platformServiceId = PLATFORM_SERVICE_IDS[opts.service];
|
|
122
126
|
if (platformServiceId) headers.set("X-Service-ID", platformServiceId);
|
|
123
127
|
applyAuthHeaders(headers, this.credential);
|
|
124
|
-
|
|
128
|
+
const requestId = generateRequestId();
|
|
129
|
+
applyAuditHeaders(headers, this.agent, requestId);
|
|
125
130
|
if (opts.headers) {
|
|
126
131
|
for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
|
|
127
132
|
}
|
|
128
133
|
const controller = new AbortController();
|
|
129
134
|
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
130
135
|
const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
|
|
136
|
+
const urlStr = url.toString();
|
|
137
|
+
const startedAt = Date.now();
|
|
138
|
+
this.emit({ phase: "request", method, service: opts.service, path: opts.path, url: urlStr, requestId });
|
|
131
139
|
let res;
|
|
132
140
|
try {
|
|
133
|
-
res = await this.fetchImpl(
|
|
141
|
+
res = await this.fetchImpl(urlStr, {
|
|
134
142
|
method,
|
|
135
143
|
headers,
|
|
136
144
|
body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
|
|
137
145
|
signal
|
|
138
146
|
});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
this.emit({
|
|
149
|
+
phase: "error",
|
|
150
|
+
method,
|
|
151
|
+
service: opts.service,
|
|
152
|
+
path: opts.path,
|
|
153
|
+
url: urlStr,
|
|
154
|
+
requestId,
|
|
155
|
+
durationMs: Date.now() - startedAt,
|
|
156
|
+
error: err instanceof Error ? err.message : String(err)
|
|
157
|
+
});
|
|
158
|
+
throw err;
|
|
139
159
|
} finally {
|
|
140
160
|
clearTimeout(timeoutId);
|
|
141
161
|
}
|
|
142
162
|
const contentType = res.headers.get("Content-Type") ?? "";
|
|
143
163
|
const parsed = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => null);
|
|
144
164
|
if (!res.ok) {
|
|
165
|
+
const durationMs = Date.now() - startedAt;
|
|
166
|
+
const message = describeError(parsed, `HTTP ${res.status}`);
|
|
167
|
+
this.emit({
|
|
168
|
+
phase: "error",
|
|
169
|
+
method,
|
|
170
|
+
service: opts.service,
|
|
171
|
+
path: opts.path,
|
|
172
|
+
url: urlStr,
|
|
173
|
+
requestId,
|
|
174
|
+
durationMs,
|
|
175
|
+
error: message,
|
|
176
|
+
status: res.status
|
|
177
|
+
});
|
|
145
178
|
if (res.status === 401) throw new TopoloAuthError(describeError(parsed, "Unauthorized"));
|
|
146
179
|
if (res.status === 403) {
|
|
147
180
|
throw new TopoloPermissionError(
|
|
@@ -151,6 +184,16 @@ var TopoloClient = class {
|
|
|
151
184
|
}
|
|
152
185
|
throw new TopoloHttpError(opts.service, opts.path, res.status, parsed);
|
|
153
186
|
}
|
|
187
|
+
this.emit({
|
|
188
|
+
phase: "response",
|
|
189
|
+
method,
|
|
190
|
+
service: opts.service,
|
|
191
|
+
path: opts.path,
|
|
192
|
+
url: urlStr,
|
|
193
|
+
requestId,
|
|
194
|
+
status: res.status,
|
|
195
|
+
durationMs: Date.now() - startedAt
|
|
196
|
+
});
|
|
154
197
|
return parsed;
|
|
155
198
|
}
|
|
156
199
|
/**
|
|
@@ -308,6 +351,13 @@ function resolveServiceUrlsFromEnv() {
|
|
|
308
351
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
309
352
|
}
|
|
310
353
|
|
|
354
|
+
// src/server.ts
|
|
355
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
356
|
+
import {
|
|
357
|
+
CallToolRequestSchema,
|
|
358
|
+
ListToolsRequestSchema
|
|
359
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
360
|
+
|
|
311
361
|
// src/gating.ts
|
|
312
362
|
function hasScope(set, required) {
|
|
313
363
|
if (set.role === "super_admin") return true;
|
|
@@ -409,37 +459,12 @@ function filterToolsByScopes(tools, scopes) {
|
|
|
409
459
|
// src/version.ts
|
|
410
460
|
var MCP_VERSION = "0.1.0";
|
|
411
461
|
|
|
412
|
-
// src/
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
if (!credential) {
|
|
416
|
-
process.stderr.write(
|
|
417
|
-
"TopoloMCP: TOPOLO_API_KEY or TOPOLO_ACCESS_TOKEN must be set in the environment.\n"
|
|
418
|
-
);
|
|
419
|
-
process.exit(1);
|
|
420
|
-
}
|
|
421
|
-
const agentName = process.env.TOPOLO_AGENT_NAME;
|
|
422
|
-
const serviceUrls = resolveServiceUrlsFromEnv();
|
|
423
|
-
const topolo = createTopolo({
|
|
424
|
-
credential,
|
|
425
|
-
agent: {
|
|
426
|
-
clientName: "topolo-mcp",
|
|
427
|
-
clientVersion: MCP_VERSION,
|
|
428
|
-
...agentName ? { agentName } : {}
|
|
429
|
-
},
|
|
430
|
-
serviceUrls,
|
|
431
|
-
requireConfirmForWrites: true
|
|
432
|
-
});
|
|
433
|
-
const scopes = await loadScopes(topolo);
|
|
434
|
-
const availableTools = filterToolsByScopes(TOOLS, scopes);
|
|
462
|
+
// src/server.ts
|
|
463
|
+
function buildServer(topolo, scopes, tools = TOOLS) {
|
|
464
|
+
const availableTools = filterToolsByScopes(tools, scopes);
|
|
435
465
|
const server = new Server(
|
|
436
|
-
{
|
|
437
|
-
|
|
438
|
-
version: MCP_VERSION
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
capabilities: { tools: {} }
|
|
442
|
-
}
|
|
466
|
+
{ name: "topolo", version: MCP_VERSION },
|
|
467
|
+
{ capabilities: { tools: {} } }
|
|
443
468
|
);
|
|
444
469
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
445
470
|
tools: availableTools.map((t) => ({
|
|
@@ -469,9 +494,7 @@ async function main() {
|
|
|
469
494
|
try {
|
|
470
495
|
const result = await tool.handler(topolo, params.arguments ?? {});
|
|
471
496
|
return {
|
|
472
|
-
content: [
|
|
473
|
-
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
474
|
-
]
|
|
497
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
475
498
|
};
|
|
476
499
|
} catch (err) {
|
|
477
500
|
if (err instanceof TopoloPermissionError) {
|
|
@@ -483,10 +506,46 @@ async function main() {
|
|
|
483
506
|
return toolError(err instanceof Error ? err.message : String(err));
|
|
484
507
|
}
|
|
485
508
|
});
|
|
509
|
+
return server;
|
|
510
|
+
}
|
|
511
|
+
function advertisedToolNames(scopes, tools = TOOLS) {
|
|
512
|
+
return filterToolsByScopes(tools, scopes).map((t) => t.name);
|
|
513
|
+
}
|
|
514
|
+
function toolError(message) {
|
|
515
|
+
return {
|
|
516
|
+
isError: true,
|
|
517
|
+
content: [{ type: "text", text: message }]
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/index.ts
|
|
522
|
+
async function main() {
|
|
523
|
+
const credential = resolveCredentialFromEnv();
|
|
524
|
+
if (!credential) {
|
|
525
|
+
process.stderr.write(
|
|
526
|
+
"TopoloMCP: TOPOLO_API_KEY or TOPOLO_ACCESS_TOKEN must be set in the environment.\n"
|
|
527
|
+
);
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
const agentName = process.env.TOPOLO_AGENT_NAME;
|
|
531
|
+
const serviceUrls = resolveServiceUrlsFromEnv();
|
|
532
|
+
const topolo = createTopolo({
|
|
533
|
+
credential,
|
|
534
|
+
agent: {
|
|
535
|
+
clientName: "topolo-mcp",
|
|
536
|
+
clientVersion: MCP_VERSION,
|
|
537
|
+
...agentName ? { agentName } : {}
|
|
538
|
+
},
|
|
539
|
+
serviceUrls,
|
|
540
|
+
requireConfirmForWrites: true
|
|
541
|
+
});
|
|
542
|
+
const scopes = await loadScopes(topolo);
|
|
543
|
+
const server = buildServer(topolo, scopes);
|
|
486
544
|
const transport = new StdioServerTransport();
|
|
487
545
|
await server.connect(transport);
|
|
546
|
+
const toolNames = advertisedToolNames(scopes);
|
|
488
547
|
process.stderr.write(
|
|
489
|
-
`TopoloMCP v${MCP_VERSION} connected. Tools available: ${
|
|
548
|
+
`TopoloMCP v${MCP_VERSION} connected. Tools available: ${toolNames.join(", ") || "(none)"}
|
|
490
549
|
`
|
|
491
550
|
);
|
|
492
551
|
}
|
|
@@ -506,12 +565,6 @@ async function loadScopes(topolo) {
|
|
|
506
565
|
throw err;
|
|
507
566
|
}
|
|
508
567
|
}
|
|
509
|
-
function toolError(message) {
|
|
510
|
-
return {
|
|
511
|
-
isError: true,
|
|
512
|
-
content: [{ type: "text", text: message }]
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
568
|
main().catch((err) => {
|
|
516
569
|
process.stderr.write(`TopoloMCP fatal: ${err instanceof Error ? err.message : String(err)}
|
|
517
570
|
`);
|
package/package.json
CHANGED