apcore-mcp 0.6.0 → 0.7.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/README.md +181 -13
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/jwt.d.ts +59 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/jwt.js +95 -0
- package/dist/auth/jwt.js.map +1 -0
- package/dist/auth/storage.d.ts +18 -0
- package/dist/auth/storage.d.ts.map +1 -0
- package/dist/auth/storage.js +19 -0
- package/dist/auth/storage.js.map +1 -0
- package/dist/auth/types.d.ts +21 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +8 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +37 -2
- package/dist/cli.js.map +1 -1
- package/dist/explorer/handler.d.ts +10 -1
- package/dist/explorer/handler.d.ts.map +1 -1
- package/dist/explorer/handler.js +47 -21
- package/dist/explorer/handler.js.map +1 -1
- package/dist/explorer/html.d.ts +2 -2
- package/dist/explorer/html.d.ts.map +1 -1
- package/dist/explorer/html.js +126 -47
- package/dist/explorer/html.js.map +1 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/dist/server/context.d.ts +5 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +7 -5
- package/dist/server/context.js.map +1 -1
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +4 -2
- package/dist/server/router.js.map +1 -1
- package/dist/server/transport.d.ts +34 -1
- package/dist/server/transport.d.ts.map +1 -1
- package/dist/server/transport.js +149 -60
- package/dist/server/transport.js.map +1 -1
- package/package.json +6 -11
|
@@ -13,12 +13,16 @@
|
|
|
13
13
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
14
14
|
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
15
15
|
import type { ExecutionRouter } from "../server/router.js";
|
|
16
|
+
import type { Authenticator } from "../auth/types.js";
|
|
17
|
+
import type { Identity } from "apcore-js";
|
|
16
18
|
/** Options for creating an ExplorerHandler. */
|
|
17
19
|
export interface ExplorerHandlerOptions {
|
|
18
20
|
/** Whether to allow tool execution from the explorer UI. Default: false */
|
|
19
21
|
allowExecute?: boolean;
|
|
20
22
|
/** URL prefix for the explorer. Default: "/explorer" */
|
|
21
23
|
prefix?: string;
|
|
24
|
+
/** Optional authenticator for explorer POST calls. */
|
|
25
|
+
authenticator?: Authenticator;
|
|
22
26
|
}
|
|
23
27
|
export declare class ExplorerHandler {
|
|
24
28
|
private readonly _toolsByName;
|
|
@@ -26,15 +30,20 @@ export declare class ExplorerHandler {
|
|
|
26
30
|
private readonly _router;
|
|
27
31
|
private readonly _allowExecute;
|
|
28
32
|
private readonly _prefix;
|
|
33
|
+
private readonly _authenticator?;
|
|
29
34
|
constructor(tools: Tool[], router: ExecutionRouter, options?: ExplorerHandlerOptions);
|
|
30
35
|
/** The URL prefix this handler is mounted at. */
|
|
31
36
|
get prefix(): string;
|
|
32
37
|
/**
|
|
33
38
|
* Attempt to handle an HTTP request.
|
|
34
39
|
*
|
|
40
|
+
* @param req - The incoming HTTP request
|
|
41
|
+
* @param res - The server response
|
|
42
|
+
* @param url - The parsed URL
|
|
43
|
+
* @param identity - Pre-authenticated identity from transport layer (if any)
|
|
35
44
|
* @returns true if the request was handled, false if it should be passed through
|
|
36
45
|
*/
|
|
37
|
-
handleRequest(req: IncomingMessage, res: ServerResponse, url: URL): Promise<boolean>;
|
|
46
|
+
handleRequest(req: IncomingMessage, res: ServerResponse, url: URL, identity?: Identity | null): Promise<boolean>;
|
|
38
47
|
/**
|
|
39
48
|
* Build a summary dict for a tool (used in the list endpoint).
|
|
40
49
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/explorer/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/explorer/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAG1C,+CAA+C;AAC/C,MAAM,WAAW,sBAAsB;IACrC,2EAA2E;IAC3E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAKD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAgB;gBAG9C,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,EAAE,eAAe,EACvB,OAAO,CAAC,EAAE,sBAAsB;IAUlC,iDAAiD;IACjD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;;;;;;OAQG;IACG,aAAa,CACjB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,GACzB,OAAO,CAAC,OAAO,CAAC;IAwDnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAWpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAYnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;OAEG;YACW,eAAe;CAmE9B"}
|
package/dist/explorer/handler.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { readBody } from "../server/transport.js";
|
|
14
14
|
import { EXPLORER_HTML } from "./html.js";
|
|
15
|
+
import { identityStorage } from "../auth/storage.js";
|
|
15
16
|
/** Maximum request body size for explorer call endpoint (1MB). */
|
|
16
17
|
const EXPLORER_MAX_BODY_BYTES = 1024 * 1024;
|
|
17
18
|
export class ExplorerHandler {
|
|
@@ -20,11 +21,13 @@ export class ExplorerHandler {
|
|
|
20
21
|
_router;
|
|
21
22
|
_allowExecute;
|
|
22
23
|
_prefix;
|
|
24
|
+
_authenticator;
|
|
23
25
|
constructor(tools, router, options) {
|
|
24
26
|
this._tools = tools;
|
|
25
27
|
this._router = router;
|
|
26
28
|
this._allowExecute = options?.allowExecute ?? false;
|
|
27
29
|
this._prefix = options?.prefix ?? "/explorer";
|
|
30
|
+
this._authenticator = options?.authenticator;
|
|
28
31
|
this._toolsByName = new Map(tools.map((t) => [t.name, t]));
|
|
29
32
|
}
|
|
30
33
|
/** The URL prefix this handler is mounted at. */
|
|
@@ -34,9 +37,13 @@ export class ExplorerHandler {
|
|
|
34
37
|
/**
|
|
35
38
|
* Attempt to handle an HTTP request.
|
|
36
39
|
*
|
|
40
|
+
* @param req - The incoming HTTP request
|
|
41
|
+
* @param res - The server response
|
|
42
|
+
* @param url - The parsed URL
|
|
43
|
+
* @param identity - Pre-authenticated identity from transport layer (if any)
|
|
37
44
|
* @returns true if the request was handled, false if it should be passed through
|
|
38
45
|
*/
|
|
39
|
-
async handleRequest(req, res, url) {
|
|
46
|
+
async handleRequest(req, res, url, identity) {
|
|
40
47
|
const pathname = url.pathname;
|
|
41
48
|
// Normalize: strip trailing slash for matching (except root)
|
|
42
49
|
const prefixSlash = this._prefix + "/";
|
|
@@ -56,8 +63,18 @@ export class ExplorerHandler {
|
|
|
56
63
|
}
|
|
57
64
|
// POST {prefix}/tools/{name}/call → execute tool
|
|
58
65
|
if (req.method === "POST" && pathname.startsWith(this._prefix + "/tools/") && pathname.endsWith("/call")) {
|
|
66
|
+
// Authenticate POST calls when authenticator is set
|
|
67
|
+
let callIdentity = identity ?? null;
|
|
68
|
+
if (this._authenticator && !callIdentity) {
|
|
69
|
+
callIdentity = await this._authenticator.authenticate(req);
|
|
70
|
+
if (!callIdentity) {
|
|
71
|
+
res.writeHead(401, { "Content-Type": "application/json", "WWW-Authenticate": "Bearer" });
|
|
72
|
+
res.end(JSON.stringify({ error: "Authentication required" }));
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
59
76
|
const toolName = decodeURIComponent(pathname.slice((this._prefix + "/tools/").length, -"/call".length));
|
|
60
|
-
await this._handleCallTool(req, res, toolName);
|
|
77
|
+
await this._handleCallTool(req, res, toolName, callIdentity);
|
|
61
78
|
return true;
|
|
62
79
|
}
|
|
63
80
|
// GET {prefix}/tools/{name} → tool detail
|
|
@@ -111,7 +128,7 @@ export class ExplorerHandler {
|
|
|
111
128
|
/**
|
|
112
129
|
* Handle POST {prefix}/tools/{name}/call - execute tool or return 403/404.
|
|
113
130
|
*/
|
|
114
|
-
async _handleCallTool(req, res, toolName) {
|
|
131
|
+
async _handleCallTool(req, res, toolName, identity) {
|
|
115
132
|
if (!this._allowExecute) {
|
|
116
133
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
117
134
|
res.end(JSON.stringify({
|
|
@@ -135,26 +152,35 @@ export class ExplorerHandler {
|
|
|
135
152
|
catch {
|
|
136
153
|
// Use empty body if parsing fails
|
|
137
154
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
155
|
+
const executeCall = async () => {
|
|
156
|
+
try {
|
|
157
|
+
const [content, isError, traceId] = await this._router.handleCall(toolName, body);
|
|
158
|
+
// Return MCP-compliant CallToolResult format
|
|
159
|
+
const result = {
|
|
160
|
+
content,
|
|
161
|
+
isError,
|
|
162
|
+
};
|
|
163
|
+
if (traceId) {
|
|
164
|
+
result._meta = { _trace_id: traceId };
|
|
165
|
+
}
|
|
166
|
+
res.writeHead(isError ? 500 : 200, { "Content-Type": "application/json" });
|
|
167
|
+
res.end(JSON.stringify(result));
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
console.error(`Explorer call_tool error for ${toolName}:`, err);
|
|
171
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
172
|
+
res.end(JSON.stringify({
|
|
173
|
+
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
174
|
+
isError: true,
|
|
175
|
+
}));
|
|
147
176
|
}
|
|
148
|
-
|
|
149
|
-
|
|
177
|
+
};
|
|
178
|
+
// Wrap in identityStorage so getCurrentIdentity() works during execution
|
|
179
|
+
if (identity) {
|
|
180
|
+
await identityStorage.run(identity, executeCall);
|
|
150
181
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
154
|
-
res.end(JSON.stringify({
|
|
155
|
-
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
156
|
-
isError: true,
|
|
157
|
-
}));
|
|
182
|
+
else {
|
|
183
|
+
await executeCall();
|
|
158
184
|
}
|
|
159
185
|
}
|
|
160
186
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/explorer/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/explorer/handler.ts"],"names":[],"mappings":"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"}
|
package/dist/explorer/html.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Self-contained HTML page for the MCP Tool Explorer.
|
|
2
|
+
* Self-contained HTML page for the APCore MCP Tool Explorer.
|
|
3
3
|
*
|
|
4
4
|
* Single-page application with no external dependencies.
|
|
5
5
|
* Displays registered MCP tools, their schemas, annotations,
|
|
6
6
|
* and optionally allows executing tools from the browser.
|
|
7
7
|
*/
|
|
8
|
-
export declare const EXPLORER_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>MCP Tool Explorer</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, monospace;\n background: #f5f5f5; color: #333; padding: 24px; }\n h1 { font-size: 1.4rem; margin-bottom: 16px; }\n .tool-list { list-style: none; }\n .tool-item { background: #fff; border: 1px solid #ddd; border-radius: 6px;\n padding: 12px 16px; margin-bottom: 8px; cursor: pointer; }\n .tool-item:hover { border-color: #888; }\n .tool-name { font-weight: 600; }\n .tool-desc { color: #666; font-size: 0.9rem; margin-top: 4px; }\n .hint { display: inline-block; background: #e8e8e8; padding: 2px 8px;\n border-radius: 3px; font-size: 0.75rem; margin-right: 4px; }\n .hint-readonly { background: #d4edda; color: #155724; }\n .hint-destructive { background: #f8d7da; color: #721c24; }\n .hint-idempotent { background: #cce5ff; color: #004085; }\n .detail { background: #fff; border: 1px solid #ddd; border-radius: 6px;\n padding: 16px; margin-top: 16px; display: none; }\n .detail.active { display: block; }\n .detail h2 { font-size: 1.1rem; margin-bottom: 12px; }\n .schema-label { font-weight: 600; margin-top: 12px; display: block; }\n pre { background: #282c34; color: #abb2bf; padding: 12px; border-radius: 4px;\n overflow-x: auto; font-size: 0.85rem; margin-top: 4px; }\n #loading { color: #888; }\n .try-it { margin-top: 16px; border-top: 1px solid #eee; padding-top: 16px; }\n .try-it h3 { font-size: 0.95rem; margin-bottom: 8px; }\n .input-editor { width: 100%; min-height: 120px; font-family: monospace;\n font-size: 0.85rem; padding: 10px; border: 1px solid #ddd;\n border-radius: 4px; resize: vertical; background: #fafafa; }\n .execute-btn { margin-top: 8px; padding: 8px 20px; background: #4CAF50; color: #fff;\n border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem;\n font-weight: 600; }\n .execute-btn:hover { background: #45a049; }\n .execute-btn:disabled { background: #ccc; cursor: not-allowed; }\n .result-area { margin-top: 12px; }\n .result-area pre { background: #1a2332; }\n .result-error { color: #f93e3e; }\n .result-success { color: #49cc90; }\n .exec-disabled { color: #888; font-size: 0.85rem; font-style: italic; margin-top: 16px; }\n .curl-section { margin-top: 14px; }\n .curl-block { background: #282c34; color: #abb2bf; padding: 12px; padding-right: 60px;\n border-radius: 4px; overflow-x: auto; font-size: 0.82rem;\n white-space: pre-wrap; word-break: break-all; position: relative;\n font-family: monospace; margin-top: 4px; }\n .copy-btn { position: absolute; top: 8px; right: 8px; background: #3a3f4b; color: #999;\n border: 1px solid #555; border-radius: 3px; padding: 2px 10px;\n font-size: 0.72rem; cursor: pointer; }\n .copy-btn:hover { background: #4a4f5b; color: #fff; }\n .resp-header { display: flex; align-items: center; gap: 12px; margin-top: 12px; }\n .resp-tabs { display: inline-flex; }\n .resp-tab { padding: 3px 10px; background: #eee; border: 1px solid #ddd;\n cursor: pointer; font-size: 0.75rem; user-select: none; }\n .resp-tab:first-child { border-radius: 3px 0 0 3px; }\n .resp-tab:last-child { border-radius: 0 3px 3px 0; }\n .resp-tab.active { background: #555; color: #fff; border-color: #555; }\n .resp-pane { display: none; }\n .resp-pane.active { display: block; }\n</style>\n</head>\n<body>\n<h1>MCP Tool Explorer</h1>\n<div id=\"loading\">Loading tools...</div>\n<ul class=\"tool-list\" id=\"tools\"></ul>\n<div class=\"detail\" id=\"detail\"></div>\n<script>\n(function() {\n var base = window.location.pathname.replace(/\\/$/, '');\n var toolsEl = document.getElementById('tools');\n var detailEl = document.getElementById('detail');\n var loadingEl = document.getElementById('loading');\n var executeEnabled = null;\n\n function esc(s) {\n var d = document.createElement('div');\n d.appendChild(document.createTextNode(s));\n return d.innerHTML;\n }\n\n function defaultFromSchema(schema) {\n if (!schema || !schema.properties) return {};\n var result = {};\n var props = schema.properties;\n for (var key in props) {\n if (!props.hasOwnProperty(key)) continue;\n var t = props[key].type;\n if (props[key]['default'] != null) {\n result[key] = props[key]['default'];\n } else if (t === 'string') {\n result[key] = '';\n } else if (t === 'number' || t === 'integer') {\n result[key] = 0;\n } else if (t === 'boolean') {\n result[key] = false;\n } else if (t === 'array') {\n result[key] = [];\n } else if (t === 'object') {\n result[key] = {};\n } else {\n result[key] = null;\n }\n }\n return result;\n }\n\n function hintsHtml(annotations) {\n if (!annotations) return '';\n var parts = [];\n if (annotations.readOnlyHint) parts.push('<span class=\"hint hint-readonly\">readOnly</span>');\n if (annotations.destructiveHint) parts.push('<span class=\"hint hint-destructive\">destructive</span>');\n if (annotations.idempotentHint) parts.push('<span class=\"hint hint-idempotent\">idempotent</span>');\n if (annotations.openWorldHint === false) parts.push('<span class=\"hint\">closedWorld</span>');\n return parts.join('');\n }\n\n fetch(base + '/tools')\n .then(function(r) { return r.json(); })\n .then(function(tools) {\n loadingEl.style.display = 'none';\n tools.forEach(function(t) {\n var li = document.createElement('li');\n li.className = 'tool-item';\n li.innerHTML =\n '<span class=\"tool-name\">' + esc(t.name) + '</span> ' +\n hintsHtml(t.annotations) +\n '<div class=\"tool-desc\">' + esc(t.description || '') + '</div>';\n li.onclick = function() { loadDetail(t.name); };\n toolsEl.appendChild(li);\n });\n fetch(base + '/tools/__probe__/call', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: '{}'\n }).then(function(r) {\n executeEnabled = r.status !== 403;\n }).catch(function() {});\n })\n .catch(function(e) { loadingEl.textContent = 'Error: ' + e; });\n\n function loadDetail(name) {\n fetch(base + '/tools/' + encodeURIComponent(name))\n .then(function(r) { return r.json(); })\n .then(function(d) {\n detailEl.className = 'detail active';\n var html =\n '<h2>' + esc(d.name) + '</h2>' +\n '<p>' + esc(d.description || '') + '</p>' +\n '<span class=\"schema-label\">Input Schema</span>' +\n '<pre>' + esc(JSON.stringify(d.inputSchema, null, 2)) + '</pre>';\n\n if (d.annotations) {\n html += '<span class=\"schema-label\">Annotations</span>' +\n '<pre>' + esc(JSON.stringify(d.annotations, null, 2)) + '</pre>';\n }\n\n html += '<div class=\"try-it\" id=\"try-it-section\">' +\n '<h3>Try it</h3>' +\n '<textarea class=\"input-editor\" id=\"input-editor\">' +\n esc(JSON.stringify(defaultFromSchema(d.inputSchema), null, 2)) +\n '</textarea>' +\n '<button class=\"execute-btn\" id=\"execute-btn\">Execute</button>' +\n '<div class=\"result-area\" id=\"result-area\"></div>' +\n '</div>';\n\n detailEl.innerHTML = html;\n\n document.getElementById('execute-btn').onclick = function() {\n execTool(d.name);\n };\n\n if (executeEnabled === false) {\n var section = document.getElementById('try-it-section');\n if (section) section.innerHTML =\n '<p class=\"exec-disabled\">' +\n 'Tool execution is disabled. ' +\n 'Launch with --allow-execute to enable.</p>';\n }\n })\n .catch(function(e) {\n detailEl.className = 'detail active';\n detailEl.innerHTML = '<p class=\"result-error\">Failed to load tool details: ' + esc(e.message) + '</p>';\n });\n }\n\n function execTool(name) {\n var btn = document.getElementById('execute-btn');\n var editor = document.getElementById('input-editor');\n var resultArea = document.getElementById('result-area');\n\n var inputText = editor.value.trim();\n var inputs;\n try {\n inputs = inputText ? JSON.parse(inputText) : {};\n } catch (e) {\n resultArea.innerHTML = '<p class=\"result-error\">Invalid JSON: ' + esc(e.message) + '</p>';\n return;\n }\n\n btn.disabled = true;\n btn.textContent = 'Executing...';\n resultArea.innerHTML = '';\n\n var bodyStr = JSON.stringify(inputs);\n var callUrl = window.location.origin + base + '/tools/' + encodeURIComponent(name) + '/call';\n var curlBody = bodyStr.replace(/'/g, \"'\\\\''\");\n var curlParts = [\n \"curl -X POST '\" + callUrl + \"'\",\n \" -H 'Content-Type: application/json'\",\n \" -d '\" + curlBody + \"'\"\n ];\n var curlCmd = curlParts.join(' \\\\\\n');\n\n fetch(base + '/tools/' + encodeURIComponent(name) + '/call', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: bodyStr\n })\n .then(function(r) {\n if (r.status === 403) {\n executeEnabled = false;\n var section = document.getElementById('try-it-section');\n if (section) section.innerHTML =\n '<p class=\"exec-disabled\">' +\n 'Tool execution is disabled. ' +\n 'Launch with --allow-execute to enable.</p>';\n return null;\n }\n return r.json().then(function(data) { return {status: r.status, data: data}; });\n })\n .then(function(result) {\n if (!result) return;\n btn.disabled = false;\n btn.textContent = 'Execute';\n var data = result.data;\n var html = '';\n\n html += '<div class=\"curl-section\">' +\n '<span class=\"schema-label\">cURL</span>' +\n '<div class=\"curl-block\"><code class=\"curl-cmd\">' + esc(curlCmd) +\n '</code><button class=\"copy-btn\" id=\"copy-curl-btn\">Copy</button></div></div>';\n\n if (data.isError) {\n var errText = (data.content || []).map(function(c) { return c.text || ''; }).join('\\n');\n html += '<span class=\"schema-label result-error\">Response — Error</span>' +\n '<pre>' + esc(errText) + '</pre>';\n } else {\n var texts = (data.content || []).filter(function(c) { return c.type === 'text'; });\n var display = texts.map(function(c) {\n try { return JSON.parse(c.text); } catch(e) { return c.text; }\n });\n var output = display.length === 1 ? display[0] : display;\n var friendlyJson = JSON.stringify(output, null, 2);\n var rawJson = JSON.stringify(data, null, 2);\n\n html += '<div class=\"resp-header\">' +\n '<span class=\"schema-label result-success\" style=\"margin:0\">Response</span>' +\n '<span class=\"resp-tabs\">' +\n '<span class=\"resp-tab active\" data-tab=\"friendly\">Result</span>' +\n '<span class=\"resp-tab\" data-tab=\"raw\">Raw MCP</span>' +\n '</span></div>' +\n '<pre class=\"resp-pane active\" data-pane=\"friendly\">' + esc(friendlyJson) + '</pre>' +\n '<pre class=\"resp-pane\" data-pane=\"raw\">' + esc(rawJson) + '</pre>';\n }\n\n resultArea.innerHTML = html;\n\n var copyBtn = document.getElementById('copy-curl-btn');\n if (copyBtn) {\n copyBtn.onclick = function() {\n var cmd = resultArea.querySelector('.curl-cmd');\n if (cmd && navigator.clipboard) {\n navigator.clipboard.writeText(cmd.textContent).then(function() {\n copyBtn.textContent = 'Copied!';\n setTimeout(function() { copyBtn.textContent = 'Copy'; }, 1500);\n });\n }\n };\n }\n var tabs = resultArea.querySelectorAll('.resp-tab');\n for (var i = 0; i < tabs.length; i++) {\n (function(tab) {\n tab.onclick = function() {\n var target = tab.getAttribute('data-tab');\n var allTabs = resultArea.querySelectorAll('.resp-tab');\n var allPanes = resultArea.querySelectorAll('.resp-pane');\n for (var j = 0; j < allTabs.length; j++) {\n allTabs[j].className = allTabs[j].getAttribute('data-tab') === target\n ? 'resp-tab active' : 'resp-tab';\n }\n for (var j = 0; j < allPanes.length; j++) {\n allPanes[j].className = allPanes[j].getAttribute('data-pane') === target\n ? 'resp-pane active' : 'resp-pane';\n }\n };\n })(tabs[i]);\n }\n })\n .catch(function(e) {\n btn.disabled = false;\n btn.textContent = 'Execute';\n resultArea.innerHTML = '<p class=\"result-error\">Request failed: ' + esc(e.message) + '</p>';\n });\n }\n})();\n</script>\n</body>\n</html>\n";
|
|
8
|
+
export declare const EXPLORER_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>APCore MCP Tool Explorer</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, monospace;\n background: #f5f5f5; color: #333; padding: 24px; }\n h1 { font-size: 1.4rem; margin-bottom: 16px; }\n .tool-list { list-style: none; }\n .tool-item { background: #fff; border: 1px solid #ddd; border-radius: 6px;\n margin-bottom: 8px; overflow: hidden; }\n .tool-header { padding: 12px 16px; cursor: pointer; display: flex;\n align-items: flex-start; gap: 8px; }\n .tool-header:hover { background: #fafafa; }\n .tool-toggle { color: #999; font-size: 0.7rem; margin-top: 3px; transition: transform 0.2s; flex-shrink: 0; }\n .tool-item.expanded .tool-toggle { transform: rotate(90deg); }\n .tool-item.expanded { border-color: #888; }\n .tool-summary { flex: 1; }\n .tool-name { font-weight: 600; }\n .tool-desc { color: #666; font-size: 0.9rem; margin-top: 4px; }\n .hint { display: inline-block; background: #e8e8e8; padding: 2px 8px;\n border-radius: 3px; font-size: 0.75rem; margin-right: 4px; }\n .hint-readonly { background: #d4edda; color: #155724; }\n .hint-destructive { background: #f8d7da; color: #721c24; }\n .hint-idempotent { background: #cce5ff; color: #004085; }\n .tool-detail { display: none; padding: 0 16px 16px; border-top: 1px solid #eee; }\n .tool-item.expanded .tool-detail { display: block; }\n .detail-title { font-size: 1.1rem; margin: 12px 0; }\n .schema-label { font-weight: 600; margin-top: 12px; display: block; }\n pre { background: #282c34; color: #abb2bf; padding: 12px; border-radius: 4px;\n overflow-x: auto; font-size: 0.85rem; margin-top: 4px; }\n #loading { color: #888; }\n .try-it { margin-top: 16px; border-top: 1px solid #eee; padding-top: 16px; }\n .try-it h3 { font-size: 0.95rem; margin-bottom: 8px; }\n .input-editor { width: 100%; min-height: 120px; font-family: monospace;\n font-size: 0.85rem; padding: 10px; border: 1px solid #ddd;\n border-radius: 4px; resize: vertical; background: #fafafa; }\n .execute-btn { margin-top: 8px; padding: 8px 20px; background: #4CAF50; color: #fff;\n border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem;\n font-weight: 600; }\n .execute-btn:hover { background: #45a049; }\n .execute-btn:disabled { background: #ccc; cursor: not-allowed; }\n .result-area { margin-top: 12px; }\n .result-area pre { background: #1a2332; }\n .result-error { color: #f93e3e; }\n .result-success { color: #49cc90; }\n .exec-disabled { color: #888; font-size: 0.85rem; font-style: italic; margin-top: 16px; }\n .auth-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px;\n padding: 10px 14px; background: #fff; border: 1px solid #ddd;\n border-radius: 6px; }\n .auth-bar label { font-weight: 600; font-size: 0.85rem; white-space: nowrap; }\n .auth-bar input { flex: 1; font-family: monospace; font-size: 0.82rem;\n padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; }\n .auth-bar .auth-status { font-size: 0.75rem; padding: 2px 8px; border-radius: 3px; }\n .auth-bar .auth-locked { background: #d4edda; color: #155724; }\n .auth-bar .auth-unlocked { background: #f8d7da; color: #721c24; }\n .curl-section { margin-top: 14px; }\n .curl-block { background: #282c34; color: #abb2bf; padding: 12px; padding-right: 60px;\n border-radius: 4px; overflow-x: auto; font-size: 0.82rem;\n white-space: pre-wrap; word-break: break-all; position: relative;\n font-family: monospace; margin-top: 4px; }\n .copy-btn { position: absolute; top: 8px; right: 8px; background: #3a3f4b; color: #999;\n border: 1px solid #555; border-radius: 3px; padding: 2px 10px;\n font-size: 0.72rem; cursor: pointer; }\n .copy-btn:hover { background: #4a4f5b; color: #fff; }\n .resp-header { display: flex; align-items: center; gap: 12px; margin-top: 12px; }\n .resp-tabs { display: inline-flex; }\n .resp-tab { padding: 3px 10px; background: #eee; border: 1px solid #ddd;\n cursor: pointer; font-size: 0.75rem; user-select: none; }\n .resp-tab:first-child { border-radius: 3px 0 0 3px; }\n .resp-tab:last-child { border-radius: 0 3px 3px 0; }\n .resp-tab.active { background: #555; color: #fff; border-color: #555; }\n .resp-pane { display: none; }\n .resp-pane.active { display: block; }\n</style>\n</head>\n<body>\n<h1>APCore MCP Tool Explorer</h1>\n<div class=\"auth-bar\" id=\"auth-bar\">\n <label for=\"auth-token\">Authorization</label>\n <input type=\"text\" id=\"auth-token\" placeholder=\"Bearer eyJhbGci...\">\n <span class=\"auth-status auth-unlocked\" id=\"auth-status\">No token</span>\n</div>\n<div id=\"loading\">Loading tools...</div>\n<ul class=\"tool-list\" id=\"tools\"></ul>\n<script>\n(function() {\n var base = window.location.pathname.replace(/\\/$/, '');\n var toolsEl = document.getElementById('tools');\n var loadingEl = document.getElementById('loading');\n var authInput = document.getElementById('auth-token');\n var authStatus = document.getElementById('auth-status');\n var executeEnabled = null;\n\n function getAuthHeaders() {\n var token = (authInput.value || '').trim();\n if (!token) return {};\n if (token.toLowerCase().indexOf('bearer ') !== 0) token = 'Bearer ' + token;\n return {'Authorization': token};\n }\n\n function updateAuthStatus() {\n var token = (authInput.value || '').trim();\n if (token) {\n authStatus.textContent = 'Token set';\n authStatus.className = 'auth-status auth-locked';\n } else {\n authStatus.textContent = 'No token';\n authStatus.className = 'auth-status auth-unlocked';\n }\n }\n authInput.addEventListener('input', updateAuthStatus);\n\n function esc(s) {\n var d = document.createElement('div');\n d.appendChild(document.createTextNode(s));\n return d.innerHTML;\n }\n\n function defaultFromSchema(schema) {\n if (!schema || !schema.properties) return {};\n var result = {};\n var props = schema.properties;\n for (var key in props) {\n if (!props.hasOwnProperty(key)) continue;\n var t = props[key].type;\n if (props[key]['default'] != null) {\n result[key] = props[key]['default'];\n } else if (t === 'string') {\n result[key] = '';\n } else if (t === 'number' || t === 'integer') {\n result[key] = 0;\n } else if (t === 'boolean') {\n result[key] = false;\n } else if (t === 'array') {\n result[key] = [];\n } else if (t === 'object') {\n result[key] = {};\n } else {\n result[key] = null;\n }\n }\n return result;\n }\n\n function hintsHtml(annotations) {\n if (!annotations) return '';\n var parts = [];\n if (annotations.readOnlyHint) parts.push('<span class=\"hint hint-readonly\">readOnly</span>');\n if (annotations.destructiveHint) parts.push('<span class=\"hint hint-destructive\">destructive</span>');\n if (annotations.idempotentHint) parts.push('<span class=\"hint hint-idempotent\">idempotent</span>');\n if (annotations.openWorldHint === false) parts.push('<span class=\"hint\">closedWorld</span>');\n return parts.join('');\n }\n\n fetch(base + '/tools', {headers: getAuthHeaders()})\n .then(function(r) { return r.json(); })\n .then(function(tools) {\n loadingEl.style.display = 'none';\n tools.forEach(function(t) {\n var li = document.createElement('li');\n li.className = 'tool-item';\n li.setAttribute('data-tool', t.name);\n li.innerHTML =\n '<div class=\"tool-header\">' +\n '<span class=\"tool-toggle\">▶</span>' +\n '<div class=\"tool-summary\">' +\n '<span class=\"tool-name\">' + esc(t.name) + '</span> ' +\n hintsHtml(t.annotations) +\n '<div class=\"tool-desc\">' + esc(t.description || '') + '</div>' +\n '</div>' +\n '</div>' +\n '<div class=\"tool-detail\"></div>';\n li.querySelector('.tool-header').onclick = function() { toggleTool(li, t.name); };\n toolsEl.appendChild(li);\n });\n var probeHeaders = Object.assign({'Content-Type': 'application/json'}, getAuthHeaders());\n fetch(base + '/tools/__probe__/call', {\n method: 'POST',\n headers: probeHeaders,\n body: '{}'\n }).then(function(r) {\n executeEnabled = r.status !== 403;\n }).catch(function() {});\n })\n .catch(function(e) { loadingEl.textContent = 'Error: ' + e; });\n\n function toggleTool(li, name) {\n if (li.classList.contains('expanded')) {\n li.classList.remove('expanded');\n return;\n }\n // Collapse any other expanded tool\n var prev = toolsEl.querySelector('.tool-item.expanded');\n if (prev && prev !== li) prev.classList.remove('expanded');\n li.classList.add('expanded');\n var detailEl = li.querySelector('.tool-detail');\n // Load detail if not yet loaded\n if (!detailEl.getAttribute('data-loaded')) {\n loadDetail(li, name);\n }\n }\n\n function loadDetail(li, name) {\n var detailEl = li.querySelector('.tool-detail');\n detailEl.innerHTML = '<p style=\"color:#888;padding-top:12px\">Loading...</p>';\n fetch(base + '/tools/' + encodeURIComponent(name), {headers: getAuthHeaders()})\n .then(function(r) { return r.json(); })\n .then(function(d) {\n detailEl.setAttribute('data-loaded', '1');\n var html =\n '<span class=\"schema-label\">Input Schema</span>' +\n '<pre>' + esc(JSON.stringify(d.inputSchema, null, 2)) + '</pre>';\n\n if (d.annotations) {\n html += '<span class=\"schema-label\">Annotations</span>' +\n '<pre>' + esc(JSON.stringify(d.annotations, null, 2)) + '</pre>';\n }\n\n html += '<div class=\"try-it\">' +\n '<h3>Try it</h3>' +\n '<textarea class=\"input-editor\">' +\n esc(JSON.stringify(defaultFromSchema(d.inputSchema), null, 2)) +\n '</textarea>' +\n '<button class=\"execute-btn\">Execute</button>' +\n '<div class=\"result-area\"></div>' +\n '</div>';\n\n detailEl.innerHTML = html;\n\n detailEl.querySelector('.execute-btn').onclick = function() {\n execTool(li, d.name);\n };\n\n if (executeEnabled === false) {\n var tryIt = detailEl.querySelector('.try-it');\n if (tryIt) tryIt.innerHTML =\n '<p class=\"exec-disabled\">' +\n 'Tool execution is disabled. ' +\n 'Launch with --allow-execute to enable.</p>';\n }\n })\n .catch(function(e) {\n detailEl.setAttribute('data-loaded', '1');\n detailEl.innerHTML = '<p class=\"result-error\" style=\"padding-top:12px\">' +\n 'Failed to load tool details: ' + esc(e.message) + '</p>';\n });\n }\n\n function execTool(li, name) {\n var detailEl = li.querySelector('.tool-detail');\n var btn = detailEl.querySelector('.execute-btn');\n var editor = detailEl.querySelector('.input-editor');\n var resultArea = detailEl.querySelector('.result-area');\n\n var inputText = editor.value.trim();\n var inputs;\n try {\n inputs = inputText ? JSON.parse(inputText) : {};\n } catch (e) {\n resultArea.innerHTML = '<p class=\"result-error\">Invalid JSON: ' + esc(e.message) + '</p>';\n return;\n }\n\n btn.disabled = true;\n btn.textContent = 'Executing...';\n resultArea.innerHTML = '';\n\n var bodyStr = JSON.stringify(inputs);\n var callUrl = window.location.origin + base + '/tools/' + encodeURIComponent(name) + '/call';\n var curlBody = bodyStr.replace(/'/g, \"'\\\\''\");\n var authToken = (authInput.value || '').trim();\n var curlLines = [\"curl -X POST '\" + callUrl + \"' \\\\\"];\n curlLines.push(\" -H 'Content-Type: application/json' \\\\\");\n if (authToken) {\n var bearerVal = authToken.toLowerCase().indexOf('bearer ') === 0 ? authToken : 'Bearer ' + authToken;\n curlLines.push(\" -H 'Authorization: \" + bearerVal + \"' \\\\\");\n }\n curlLines.push(\" -d '\" + curlBody + \"'\");\n var curlCmd = curlLines.join(\"\\n\");\n\n var fetchHeaders = Object.assign({'Content-Type': 'application/json'}, getAuthHeaders());\n fetch(base + '/tools/' + encodeURIComponent(name) + '/call', {\n method: 'POST',\n headers: fetchHeaders,\n body: bodyStr\n })\n .then(function(r) {\n if (r.status === 403) {\n executeEnabled = false;\n var tryIt = detailEl.querySelector('.try-it');\n if (tryIt) tryIt.innerHTML =\n '<p class=\"exec-disabled\">' +\n 'Tool execution is disabled. ' +\n 'Launch with --allow-execute to enable.</p>';\n return null;\n }\n if (r.status === 401) {\n btn.disabled = false;\n btn.textContent = 'Execute';\n resultArea.innerHTML =\n '<p class=\"result-error\">401 Unauthorized \\u2014 ' +\n 'enter a valid Bearer token in the Authorization field above.</p>';\n return null;\n }\n return r.json().then(function(data) { return {status: r.status, data: data}; });\n })\n .then(function(result) {\n if (!result) return;\n btn.disabled = false;\n btn.textContent = 'Execute';\n var data = result.data;\n var html = '';\n\n html += '<div class=\"curl-section\">' +\n '<span class=\"schema-label\">cURL</span>' +\n '<div class=\"curl-block\"><code class=\"curl-cmd\">' + esc(curlCmd) +\n '</code><button class=\"copy-btn\">Copy</button></div></div>';\n\n if (data.isError) {\n var errText = (data.content || []).map(function(c) { return c.text || ''; }).join('\\n');\n html += '<span class=\"schema-label result-error\">Response — Error</span>' +\n '<pre>' + esc(errText) + '</pre>';\n } else {\n var texts = (data.content || []).filter(function(c) { return c.type === 'text'; });\n var display = texts.map(function(c) {\n try { return JSON.parse(c.text); } catch(e) { return c.text; }\n });\n var output = display.length === 1 ? display[0] : display;\n var friendlyJson = JSON.stringify(output, null, 2);\n var rawJson = JSON.stringify(data, null, 2);\n\n html += '<div class=\"resp-header\">' +\n '<span class=\"schema-label result-success\" style=\"margin:0\">Response</span>' +\n '<span class=\"resp-tabs\">' +\n '<span class=\"resp-tab active\" data-tab=\"friendly\">Result</span>' +\n '<span class=\"resp-tab\" data-tab=\"raw\">Raw MCP</span>' +\n '</span></div>' +\n '<pre class=\"resp-pane active\" data-pane=\"friendly\">' + esc(friendlyJson) + '</pre>' +\n '<pre class=\"resp-pane\" data-pane=\"raw\">' + esc(rawJson) + '</pre>';\n }\n\n resultArea.innerHTML = html;\n\n var copyBtn = resultArea.querySelector('.copy-btn');\n if (copyBtn) {\n copyBtn.onclick = function() {\n var cmd = resultArea.querySelector('.curl-cmd');\n if (cmd && navigator.clipboard) {\n navigator.clipboard.writeText(cmd.textContent).then(function() {\n copyBtn.textContent = 'Copied!';\n setTimeout(function() { copyBtn.textContent = 'Copy'; }, 1500);\n }).catch(function() {\n copyBtn.textContent = 'Failed';\n setTimeout(function() { copyBtn.textContent = 'Copy'; }, 1500);\n });\n }\n };\n }\n var tabs = resultArea.querySelectorAll('.resp-tab');\n for (var i = 0; i < tabs.length; i++) {\n (function(tab) {\n tab.onclick = function() {\n var target = tab.getAttribute('data-tab');\n var allTabs = resultArea.querySelectorAll('.resp-tab');\n var allPanes = resultArea.querySelectorAll('.resp-pane');\n for (var j = 0; j < allTabs.length; j++) {\n allTabs[j].className = allTabs[j].getAttribute('data-tab') === target\n ? 'resp-tab active' : 'resp-tab';\n }\n for (var j = 0; j < allPanes.length; j++) {\n allPanes[j].className = allPanes[j].getAttribute('data-pane') === target\n ? 'resp-pane active' : 'resp-pane';\n }\n };\n })(tabs[i]);\n }\n })\n .catch(function(e) {\n btn.disabled = false;\n btn.textContent = 'Execute';\n resultArea.innerHTML = '<p class=\"result-error\">Request failed: ' + esc(e.message) + '</p>';\n });\n }\n})();\n</script>\n</body>\n</html>\n";
|
|
9
9
|
//# sourceMappingURL=html.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/explorer/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/explorer/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,aAAa,qphBAwYzB,CAAC"}
|
package/dist/explorer/html.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Self-contained HTML page for the MCP Tool Explorer.
|
|
2
|
+
* Self-contained HTML page for the APCore MCP Tool Explorer.
|
|
3
3
|
*
|
|
4
4
|
* Single-page application with no external dependencies.
|
|
5
5
|
* Displays registered MCP tools, their schemas, annotations,
|
|
@@ -11,7 +11,7 @@ export const EXPLORER_HTML = `\
|
|
|
11
11
|
<head>
|
|
12
12
|
<meta charset="utf-8">
|
|
13
13
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
14
|
-
<title>MCP Tool Explorer</title>
|
|
14
|
+
<title>APCore MCP Tool Explorer</title>
|
|
15
15
|
<style>
|
|
16
16
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
17
17
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
|
|
@@ -19,8 +19,14 @@ export const EXPLORER_HTML = `\
|
|
|
19
19
|
h1 { font-size: 1.4rem; margin-bottom: 16px; }
|
|
20
20
|
.tool-list { list-style: none; }
|
|
21
21
|
.tool-item { background: #fff; border: 1px solid #ddd; border-radius: 6px;
|
|
22
|
-
|
|
23
|
-
.tool-
|
|
22
|
+
margin-bottom: 8px; overflow: hidden; }
|
|
23
|
+
.tool-header { padding: 12px 16px; cursor: pointer; display: flex;
|
|
24
|
+
align-items: flex-start; gap: 8px; }
|
|
25
|
+
.tool-header:hover { background: #fafafa; }
|
|
26
|
+
.tool-toggle { color: #999; font-size: 0.7rem; margin-top: 3px; transition: transform 0.2s; flex-shrink: 0; }
|
|
27
|
+
.tool-item.expanded .tool-toggle { transform: rotate(90deg); }
|
|
28
|
+
.tool-item.expanded { border-color: #888; }
|
|
29
|
+
.tool-summary { flex: 1; }
|
|
24
30
|
.tool-name { font-weight: 600; }
|
|
25
31
|
.tool-desc { color: #666; font-size: 0.9rem; margin-top: 4px; }
|
|
26
32
|
.hint { display: inline-block; background: #e8e8e8; padding: 2px 8px;
|
|
@@ -28,10 +34,9 @@ export const EXPLORER_HTML = `\
|
|
|
28
34
|
.hint-readonly { background: #d4edda; color: #155724; }
|
|
29
35
|
.hint-destructive { background: #f8d7da; color: #721c24; }
|
|
30
36
|
.hint-idempotent { background: #cce5ff; color: #004085; }
|
|
31
|
-
.detail {
|
|
32
|
-
|
|
33
|
-
.detail
|
|
34
|
-
.detail h2 { font-size: 1.1rem; margin-bottom: 12px; }
|
|
37
|
+
.tool-detail { display: none; padding: 0 16px 16px; border-top: 1px solid #eee; }
|
|
38
|
+
.tool-item.expanded .tool-detail { display: block; }
|
|
39
|
+
.detail-title { font-size: 1.1rem; margin: 12px 0; }
|
|
35
40
|
.schema-label { font-weight: 600; margin-top: 12px; display: block; }
|
|
36
41
|
pre { background: #282c34; color: #abb2bf; padding: 12px; border-radius: 4px;
|
|
37
42
|
overflow-x: auto; font-size: 0.85rem; margin-top: 4px; }
|
|
@@ -51,6 +56,15 @@ export const EXPLORER_HTML = `\
|
|
|
51
56
|
.result-error { color: #f93e3e; }
|
|
52
57
|
.result-success { color: #49cc90; }
|
|
53
58
|
.exec-disabled { color: #888; font-size: 0.85rem; font-style: italic; margin-top: 16px; }
|
|
59
|
+
.auth-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px;
|
|
60
|
+
padding: 10px 14px; background: #fff; border: 1px solid #ddd;
|
|
61
|
+
border-radius: 6px; }
|
|
62
|
+
.auth-bar label { font-weight: 600; font-size: 0.85rem; white-space: nowrap; }
|
|
63
|
+
.auth-bar input { flex: 1; font-family: monospace; font-size: 0.82rem;
|
|
64
|
+
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; }
|
|
65
|
+
.auth-bar .auth-status { font-size: 0.75rem; padding: 2px 8px; border-radius: 3px; }
|
|
66
|
+
.auth-bar .auth-locked { background: #d4edda; color: #155724; }
|
|
67
|
+
.auth-bar .auth-unlocked { background: #f8d7da; color: #721c24; }
|
|
54
68
|
.curl-section { margin-top: 14px; }
|
|
55
69
|
.curl-block { background: #282c34; color: #abb2bf; padding: 12px; padding-right: 60px;
|
|
56
70
|
border-radius: 4px; overflow-x: auto; font-size: 0.82rem;
|
|
@@ -72,18 +86,42 @@ export const EXPLORER_HTML = `\
|
|
|
72
86
|
</style>
|
|
73
87
|
</head>
|
|
74
88
|
<body>
|
|
75
|
-
<h1>MCP Tool Explorer</h1>
|
|
89
|
+
<h1>APCore MCP Tool Explorer</h1>
|
|
90
|
+
<div class="auth-bar" id="auth-bar">
|
|
91
|
+
<label for="auth-token">Authorization</label>
|
|
92
|
+
<input type="text" id="auth-token" placeholder="Bearer eyJhbGci...">
|
|
93
|
+
<span class="auth-status auth-unlocked" id="auth-status">No token</span>
|
|
94
|
+
</div>
|
|
76
95
|
<div id="loading">Loading tools...</div>
|
|
77
96
|
<ul class="tool-list" id="tools"></ul>
|
|
78
|
-
<div class="detail" id="detail"></div>
|
|
79
97
|
<script>
|
|
80
98
|
(function() {
|
|
81
99
|
var base = window.location.pathname.replace(/\\/$/, '');
|
|
82
100
|
var toolsEl = document.getElementById('tools');
|
|
83
|
-
var detailEl = document.getElementById('detail');
|
|
84
101
|
var loadingEl = document.getElementById('loading');
|
|
102
|
+
var authInput = document.getElementById('auth-token');
|
|
103
|
+
var authStatus = document.getElementById('auth-status');
|
|
85
104
|
var executeEnabled = null;
|
|
86
105
|
|
|
106
|
+
function getAuthHeaders() {
|
|
107
|
+
var token = (authInput.value || '').trim();
|
|
108
|
+
if (!token) return {};
|
|
109
|
+
if (token.toLowerCase().indexOf('bearer ') !== 0) token = 'Bearer ' + token;
|
|
110
|
+
return {'Authorization': token};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function updateAuthStatus() {
|
|
114
|
+
var token = (authInput.value || '').trim();
|
|
115
|
+
if (token) {
|
|
116
|
+
authStatus.textContent = 'Token set';
|
|
117
|
+
authStatus.className = 'auth-status auth-locked';
|
|
118
|
+
} else {
|
|
119
|
+
authStatus.textContent = 'No token';
|
|
120
|
+
authStatus.className = 'auth-status auth-unlocked';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
authInput.addEventListener('input', updateAuthStatus);
|
|
124
|
+
|
|
87
125
|
function esc(s) {
|
|
88
126
|
var d = document.createElement('div');
|
|
89
127
|
d.appendChild(document.createTextNode(s));
|
|
@@ -126,23 +164,31 @@ export const EXPLORER_HTML = `\
|
|
|
126
164
|
return parts.join('');
|
|
127
165
|
}
|
|
128
166
|
|
|
129
|
-
fetch(base + '/tools')
|
|
167
|
+
fetch(base + '/tools', {headers: getAuthHeaders()})
|
|
130
168
|
.then(function(r) { return r.json(); })
|
|
131
169
|
.then(function(tools) {
|
|
132
170
|
loadingEl.style.display = 'none';
|
|
133
171
|
tools.forEach(function(t) {
|
|
134
172
|
var li = document.createElement('li');
|
|
135
173
|
li.className = 'tool-item';
|
|
174
|
+
li.setAttribute('data-tool', t.name);
|
|
136
175
|
li.innerHTML =
|
|
137
|
-
'<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
176
|
+
'<div class="tool-header">' +
|
|
177
|
+
'<span class="tool-toggle">▶</span>' +
|
|
178
|
+
'<div class="tool-summary">' +
|
|
179
|
+
'<span class="tool-name">' + esc(t.name) + '</span> ' +
|
|
180
|
+
hintsHtml(t.annotations) +
|
|
181
|
+
'<div class="tool-desc">' + esc(t.description || '') + '</div>' +
|
|
182
|
+
'</div>' +
|
|
183
|
+
'</div>' +
|
|
184
|
+
'<div class="tool-detail"></div>';
|
|
185
|
+
li.querySelector('.tool-header').onclick = function() { toggleTool(li, t.name); };
|
|
141
186
|
toolsEl.appendChild(li);
|
|
142
187
|
});
|
|
188
|
+
var probeHeaders = Object.assign({'Content-Type': 'application/json'}, getAuthHeaders());
|
|
143
189
|
fetch(base + '/tools/__probe__/call', {
|
|
144
190
|
method: 'POST',
|
|
145
|
-
headers:
|
|
191
|
+
headers: probeHeaders,
|
|
146
192
|
body: '{}'
|
|
147
193
|
}).then(function(r) {
|
|
148
194
|
executeEnabled = r.status !== 403;
|
|
@@ -150,14 +196,30 @@ export const EXPLORER_HTML = `\
|
|
|
150
196
|
})
|
|
151
197
|
.catch(function(e) { loadingEl.textContent = 'Error: ' + e; });
|
|
152
198
|
|
|
153
|
-
function
|
|
154
|
-
|
|
199
|
+
function toggleTool(li, name) {
|
|
200
|
+
if (li.classList.contains('expanded')) {
|
|
201
|
+
li.classList.remove('expanded');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Collapse any other expanded tool
|
|
205
|
+
var prev = toolsEl.querySelector('.tool-item.expanded');
|
|
206
|
+
if (prev && prev !== li) prev.classList.remove('expanded');
|
|
207
|
+
li.classList.add('expanded');
|
|
208
|
+
var detailEl = li.querySelector('.tool-detail');
|
|
209
|
+
// Load detail if not yet loaded
|
|
210
|
+
if (!detailEl.getAttribute('data-loaded')) {
|
|
211
|
+
loadDetail(li, name);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function loadDetail(li, name) {
|
|
216
|
+
var detailEl = li.querySelector('.tool-detail');
|
|
217
|
+
detailEl.innerHTML = '<p style="color:#888;padding-top:12px">Loading...</p>';
|
|
218
|
+
fetch(base + '/tools/' + encodeURIComponent(name), {headers: getAuthHeaders()})
|
|
155
219
|
.then(function(r) { return r.json(); })
|
|
156
220
|
.then(function(d) {
|
|
157
|
-
detailEl.
|
|
221
|
+
detailEl.setAttribute('data-loaded', '1');
|
|
158
222
|
var html =
|
|
159
|
-
'<h2>' + esc(d.name) + '</h2>' +
|
|
160
|
-
'<p>' + esc(d.description || '') + '</p>' +
|
|
161
223
|
'<span class="schema-label">Input Schema</span>' +
|
|
162
224
|
'<pre>' + esc(JSON.stringify(d.inputSchema, null, 2)) + '</pre>';
|
|
163
225
|
|
|
@@ -166,39 +228,41 @@ export const EXPLORER_HTML = `\
|
|
|
166
228
|
'<pre>' + esc(JSON.stringify(d.annotations, null, 2)) + '</pre>';
|
|
167
229
|
}
|
|
168
230
|
|
|
169
|
-
html += '<div class="try-it"
|
|
231
|
+
html += '<div class="try-it">' +
|
|
170
232
|
'<h3>Try it</h3>' +
|
|
171
|
-
'<textarea class="input-editor"
|
|
233
|
+
'<textarea class="input-editor">' +
|
|
172
234
|
esc(JSON.stringify(defaultFromSchema(d.inputSchema), null, 2)) +
|
|
173
235
|
'</textarea>' +
|
|
174
|
-
'<button class="execute-btn"
|
|
175
|
-
'<div class="result-area"
|
|
236
|
+
'<button class="execute-btn">Execute</button>' +
|
|
237
|
+
'<div class="result-area"></div>' +
|
|
176
238
|
'</div>';
|
|
177
239
|
|
|
178
240
|
detailEl.innerHTML = html;
|
|
179
241
|
|
|
180
|
-
|
|
181
|
-
execTool(d.name);
|
|
242
|
+
detailEl.querySelector('.execute-btn').onclick = function() {
|
|
243
|
+
execTool(li, d.name);
|
|
182
244
|
};
|
|
183
245
|
|
|
184
246
|
if (executeEnabled === false) {
|
|
185
|
-
var
|
|
186
|
-
if (
|
|
247
|
+
var tryIt = detailEl.querySelector('.try-it');
|
|
248
|
+
if (tryIt) tryIt.innerHTML =
|
|
187
249
|
'<p class="exec-disabled">' +
|
|
188
250
|
'Tool execution is disabled. ' +
|
|
189
251
|
'Launch with --allow-execute to enable.</p>';
|
|
190
252
|
}
|
|
191
253
|
})
|
|
192
254
|
.catch(function(e) {
|
|
193
|
-
detailEl.
|
|
194
|
-
detailEl.innerHTML = '<p class="result-error"
|
|
255
|
+
detailEl.setAttribute('data-loaded', '1');
|
|
256
|
+
detailEl.innerHTML = '<p class="result-error" style="padding-top:12px">' +
|
|
257
|
+
'Failed to load tool details: ' + esc(e.message) + '</p>';
|
|
195
258
|
});
|
|
196
259
|
}
|
|
197
260
|
|
|
198
|
-
function execTool(name) {
|
|
199
|
-
var
|
|
200
|
-
var
|
|
201
|
-
var
|
|
261
|
+
function execTool(li, name) {
|
|
262
|
+
var detailEl = li.querySelector('.tool-detail');
|
|
263
|
+
var btn = detailEl.querySelector('.execute-btn');
|
|
264
|
+
var editor = detailEl.querySelector('.input-editor');
|
|
265
|
+
var resultArea = detailEl.querySelector('.result-area');
|
|
202
266
|
|
|
203
267
|
var inputText = editor.value.trim();
|
|
204
268
|
var inputs;
|
|
@@ -216,28 +280,40 @@ export const EXPLORER_HTML = `\
|
|
|
216
280
|
var bodyStr = JSON.stringify(inputs);
|
|
217
281
|
var callUrl = window.location.origin + base + '/tools/' + encodeURIComponent(name) + '/call';
|
|
218
282
|
var curlBody = bodyStr.replace(/'/g, "'\\\\''");
|
|
219
|
-
var
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
283
|
+
var authToken = (authInput.value || '').trim();
|
|
284
|
+
var curlLines = ["curl -X POST '" + callUrl + "' \\\\"];
|
|
285
|
+
curlLines.push(" -H 'Content-Type: application/json' \\\\");
|
|
286
|
+
if (authToken) {
|
|
287
|
+
var bearerVal = authToken.toLowerCase().indexOf('bearer ') === 0 ? authToken : 'Bearer ' + authToken;
|
|
288
|
+
curlLines.push(" -H 'Authorization: " + bearerVal + "' \\\\");
|
|
289
|
+
}
|
|
290
|
+
curlLines.push(" -d '" + curlBody + "'");
|
|
291
|
+
var curlCmd = curlLines.join("\\n");
|
|
225
292
|
|
|
293
|
+
var fetchHeaders = Object.assign({'Content-Type': 'application/json'}, getAuthHeaders());
|
|
226
294
|
fetch(base + '/tools/' + encodeURIComponent(name) + '/call', {
|
|
227
295
|
method: 'POST',
|
|
228
|
-
headers:
|
|
296
|
+
headers: fetchHeaders,
|
|
229
297
|
body: bodyStr
|
|
230
298
|
})
|
|
231
299
|
.then(function(r) {
|
|
232
300
|
if (r.status === 403) {
|
|
233
301
|
executeEnabled = false;
|
|
234
|
-
var
|
|
235
|
-
if (
|
|
302
|
+
var tryIt = detailEl.querySelector('.try-it');
|
|
303
|
+
if (tryIt) tryIt.innerHTML =
|
|
236
304
|
'<p class="exec-disabled">' +
|
|
237
305
|
'Tool execution is disabled. ' +
|
|
238
306
|
'Launch with --allow-execute to enable.</p>';
|
|
239
307
|
return null;
|
|
240
308
|
}
|
|
309
|
+
if (r.status === 401) {
|
|
310
|
+
btn.disabled = false;
|
|
311
|
+
btn.textContent = 'Execute';
|
|
312
|
+
resultArea.innerHTML =
|
|
313
|
+
'<p class="result-error">401 Unauthorized \\u2014 ' +
|
|
314
|
+
'enter a valid Bearer token in the Authorization field above.</p>';
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
241
317
|
return r.json().then(function(data) { return {status: r.status, data: data}; });
|
|
242
318
|
})
|
|
243
319
|
.then(function(result) {
|
|
@@ -250,7 +326,7 @@ export const EXPLORER_HTML = `\
|
|
|
250
326
|
html += '<div class="curl-section">' +
|
|
251
327
|
'<span class="schema-label">cURL</span>' +
|
|
252
328
|
'<div class="curl-block"><code class="curl-cmd">' + esc(curlCmd) +
|
|
253
|
-
'</code><button class="copy-btn"
|
|
329
|
+
'</code><button class="copy-btn">Copy</button></div></div>';
|
|
254
330
|
|
|
255
331
|
if (data.isError) {
|
|
256
332
|
var errText = (data.content || []).map(function(c) { return c.text || ''; }).join('\\n');
|
|
@@ -277,7 +353,7 @@ export const EXPLORER_HTML = `\
|
|
|
277
353
|
|
|
278
354
|
resultArea.innerHTML = html;
|
|
279
355
|
|
|
280
|
-
var copyBtn =
|
|
356
|
+
var copyBtn = resultArea.querySelector('.copy-btn');
|
|
281
357
|
if (copyBtn) {
|
|
282
358
|
copyBtn.onclick = function() {
|
|
283
359
|
var cmd = resultArea.querySelector('.curl-cmd');
|
|
@@ -285,6 +361,9 @@ export const EXPLORER_HTML = `\
|
|
|
285
361
|
navigator.clipboard.writeText(cmd.textContent).then(function() {
|
|
286
362
|
copyBtn.textContent = 'Copied!';
|
|
287
363
|
setTimeout(function() { copyBtn.textContent = 'Copy'; }, 1500);
|
|
364
|
+
}).catch(function() {
|
|
365
|
+
copyBtn.textContent = 'Failed';
|
|
366
|
+
setTimeout(function() { copyBtn.textContent = 'Copy'; }, 1500);
|
|
288
367
|
});
|
|
289
368
|
}
|
|
290
369
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/explorer/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/explorer/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwY5B,CAAC"}
|