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.
Files changed (44) hide show
  1. package/README.md +181 -13
  2. package/dist/auth/index.d.ts +8 -0
  3. package/dist/auth/index.d.ts.map +1 -0
  4. package/dist/auth/index.js +6 -0
  5. package/dist/auth/index.js.map +1 -0
  6. package/dist/auth/jwt.d.ts +59 -0
  7. package/dist/auth/jwt.d.ts.map +1 -0
  8. package/dist/auth/jwt.js +95 -0
  9. package/dist/auth/jwt.js.map +1 -0
  10. package/dist/auth/storage.d.ts +18 -0
  11. package/dist/auth/storage.d.ts.map +1 -0
  12. package/dist/auth/storage.js +19 -0
  13. package/dist/auth/storage.js.map +1 -0
  14. package/dist/auth/types.d.ts +21 -0
  15. package/dist/auth/types.d.ts.map +1 -0
  16. package/dist/auth/types.js +8 -0
  17. package/dist/auth/types.js.map +1 -0
  18. package/dist/cli.d.ts.map +1 -1
  19. package/dist/cli.js +37 -2
  20. package/dist/cli.js.map +1 -1
  21. package/dist/explorer/handler.d.ts +10 -1
  22. package/dist/explorer/handler.d.ts.map +1 -1
  23. package/dist/explorer/handler.js +47 -21
  24. package/dist/explorer/handler.js.map +1 -1
  25. package/dist/explorer/html.d.ts +2 -2
  26. package/dist/explorer/html.d.ts.map +1 -1
  27. package/dist/explorer/html.js +126 -47
  28. package/dist/explorer/html.js.map +1 -1
  29. package/dist/index.d.ts +10 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +15 -5
  32. package/dist/index.js.map +1 -1
  33. package/dist/server/context.d.ts +5 -2
  34. package/dist/server/context.d.ts.map +1 -1
  35. package/dist/server/context.js +7 -5
  36. package/dist/server/context.js.map +1 -1
  37. package/dist/server/router.d.ts.map +1 -1
  38. package/dist/server/router.js +4 -2
  39. package/dist/server/router.js.map +1 -1
  40. package/dist/server/transport.d.ts +34 -1
  41. package/dist/server/transport.d.ts.map +1 -1
  42. package/dist/server/transport.js +149 -60
  43. package/dist/server/transport.js.map +1 -1
  44. 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;AAI3D,+CAA+C;AAC/C,MAAM,WAAW,sBAAsB;IACrC,2EAA2E;IAC3E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;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;gBAG/B,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,EAAE,eAAe,EACvB,OAAO,CAAC,EAAE,sBAAsB;IASlC,iDAAiD;IACjD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;;OAIG;IACG,aAAa,CACjB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,GACP,OAAO,CAAC,OAAO,CAAC;IA6CnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAWpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAYnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;OAEG;YACW,eAAe;CAyD9B"}
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"}
@@ -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
- try {
139
- const [content, isError, traceId] = await this._router.handleCall(toolName, body);
140
- // Return MCP-compliant CallToolResult format
141
- const result = {
142
- content,
143
- isError,
144
- };
145
- if (traceId) {
146
- result._meta = { _trace_id: traceId };
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
- res.writeHead(isError ? 500 : 200, { "Content-Type": "application/json" });
149
- res.end(JSON.stringify(result));
177
+ };
178
+ // Wrap in identityStorage so getCurrentIdentity() works during execution
179
+ if (identity) {
180
+ await identityStorage.run(identity, executeCall);
150
181
  }
151
- catch (err) {
152
- console.error(`Explorer call_tool error for ${toolName}:`, err);
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":"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"}
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/explorer/handler.ts"],"names":[],"mappings":"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"}
@@ -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 &mdash; 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\">&#9654;</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 &mdash; 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,g+ZAyTzB,CAAC"}
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/explorer/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,aAAa,qphBAwYzB,CAAC"}
@@ -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
- padding: 12px 16px; margin-bottom: 8px; cursor: pointer; }
23
- .tool-item:hover { border-color: #888; }
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 { background: #fff; border: 1px solid #ddd; border-radius: 6px;
32
- padding: 16px; margin-top: 16px; display: none; }
33
- .detail.active { display: block; }
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
- '<span class="tool-name">' + esc(t.name) + '</span> ' +
138
- hintsHtml(t.annotations) +
139
- '<div class="tool-desc">' + esc(t.description || '') + '</div>';
140
- li.onclick = function() { loadDetail(t.name); };
176
+ '<div class="tool-header">' +
177
+ '<span class="tool-toggle">&#9654;</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: {'Content-Type': 'application/json'},
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 loadDetail(name) {
154
- fetch(base + '/tools/' + encodeURIComponent(name))
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.className = 'detail active';
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" id="try-it-section">' +
231
+ html += '<div class="try-it">' +
170
232
  '<h3>Try it</h3>' +
171
- '<textarea class="input-editor" id="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" id="execute-btn">Execute</button>' +
175
- '<div class="result-area" id="result-area"></div>' +
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
- document.getElementById('execute-btn').onclick = function() {
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 section = document.getElementById('try-it-section');
186
- if (section) section.innerHTML =
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.className = 'detail active';
194
- detailEl.innerHTML = '<p class="result-error">Failed to load tool details: ' + esc(e.message) + '</p>';
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 btn = document.getElementById('execute-btn');
200
- var editor = document.getElementById('input-editor');
201
- var resultArea = document.getElementById('result-area');
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 curlParts = [
220
- "curl -X POST '" + callUrl + "'",
221
- " -H 'Content-Type: application/json'",
222
- " -d '" + curlBody + "'"
223
- ];
224
- var curlCmd = curlParts.join(' \\\\\\n');
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: {'Content-Type': 'application/json'},
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 section = document.getElementById('try-it-section');
235
- if (section) section.innerHTML =
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" id="copy-curl-btn">Copy</button></div></div>';
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 = document.getElementById('copy-curl-btn');
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyT5B,CAAC"}
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/explorer/html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwY5B,CAAC"}