@webmcp-auto-ui/core 0.5.0 → 2.5.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.
@@ -86,29 +86,69 @@ export class McpMultiClient {
86
86
  /**
87
87
  * List ALL tools from ALL connected servers.
88
88
  * Each tool is augmented with its origin server URL and name.
89
- * Tools with the same name from different servers are all included.
89
+ * Duplicate tool names across servers are prefixed with the server name
90
+ * (e.g. "wikipedia__search") to satisfy the Claude API uniqueness constraint.
90
91
  */
91
92
  listAllTools(): AggregatedTool[] {
93
+ // First pass: count occurrences of each tool name across all servers
94
+ const nameCounts = new Map<string, number>();
95
+ for (const [, entry] of this.servers) {
96
+ for (const tool of entry.tools) {
97
+ nameCounts.set(tool.name, (nameCounts.get(tool.name) ?? 0) + 1);
98
+ }
99
+ }
100
+
101
+ // Second pass: build result, prefixing duplicates with serverName
92
102
  const result: AggregatedTool[] = [];
93
103
  for (const [url, entry] of this.servers) {
94
104
  for (const tool of entry.tools) {
95
- result.push({ ...tool, serverUrl: url, serverName: entry.name });
105
+ const isDuplicate = (nameCounts.get(tool.name) ?? 0) > 1;
106
+ if (isDuplicate) {
107
+ const prefix = this.normalizeServerName(entry.name);
108
+ result.push({
109
+ ...tool,
110
+ name: `${prefix}__${tool.name}`,
111
+ description: `[${entry.name}] ${tool.description ?? ''}`,
112
+ serverUrl: url,
113
+ serverName: entry.name,
114
+ });
115
+ } else {
116
+ result.push({ ...tool, serverUrl: url, serverName: entry.name });
117
+ }
96
118
  }
97
119
  }
98
120
  return result;
99
121
  }
100
122
 
101
123
  /**
102
- * Call a tool by name. Automatically routes to the first server (insertion
103
- * order) that exposes a tool with the given name.
124
+ * Call a tool by name. Automatically routes to the correct server.
125
+ * Supports both plain names ("search") and prefixed names ("wikipedia__search")
126
+ * for disambiguated duplicates.
104
127
  */
105
128
  async callTool(name: string, args?: Record<string, unknown>): Promise<McpToolResult> {
129
+ // 1. Exact match on original tool name (unprefixed)
106
130
  for (const [, entry] of this.servers) {
107
131
  const match = entry.tools.find((t) => t.name === name);
108
132
  if (match) {
109
133
  return entry.client.callTool(name, args);
110
134
  }
111
135
  }
136
+
137
+ // 2. Prefixed name: "serverprefix__realToolName"
138
+ const separatorIdx = name.indexOf('__');
139
+ if (separatorIdx !== -1) {
140
+ const prefix = name.slice(0, separatorIdx);
141
+ const realName = name.slice(separatorIdx + 2);
142
+ for (const [, entry] of this.servers) {
143
+ if (this.normalizeServerName(entry.name) === prefix) {
144
+ const match = entry.tools.find((t) => t.name === realName);
145
+ if (match) {
146
+ return entry.client.callTool(realName, args);
147
+ }
148
+ }
149
+ }
150
+ }
151
+
112
152
  throw new Error(`McpMultiClient: no server exposes tool "${name}"`);
113
153
  }
114
154
 
@@ -124,6 +164,15 @@ export class McpMultiClient {
124
164
  this.servers.clear();
125
165
  }
126
166
 
167
+ // -------------------------------------------------------------------------
168
+ // Private helpers
169
+ // -------------------------------------------------------------------------
170
+
171
+ /** Convert a server name to a snake_case prefix for tool name disambiguation. */
172
+ private normalizeServerName(name: string): string {
173
+ return name.toLowerCase().replace(/[^a-z0-9_-]+/g, '_').replace(/_{2,}/g, '_').replace(/^_|_$/g, '');
174
+ }
175
+
127
176
  // -------------------------------------------------------------------------
128
177
  // Getters
129
178
  // -------------------------------------------------------------------------
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @webmcp-auto-ui/core — WebMCP Polyfill
3
+ *
4
+ * Implements the 17 MUST HAVE features of the W3C WebMCP Draft CG Report
5
+ * (2026-03-27) plus ecosystem extras.
6
+ *
7
+ * Architecture: module-level state (no class) for tree-shakability.
8
+ * Zero external dependencies.
9
+ *
10
+ * @license AGPL-3.0-or-later
11
+ */
12
+ import type { ToolExecuteResult, WebMCPPolyfillOptions } from './types.js';
13
+ /**
14
+ * Execute a registered tool by name.
15
+ * Steps:
16
+ * 1. Look up tool in map
17
+ * 2. Validate input against inputSchema (Feature 8)
18
+ * 3. Inject authContext if configured (Extra)
19
+ * 4. Apply confirmation policy if applicable (Extra)
20
+ * 5. Create a ModelContextClient (Feature 6)
21
+ * 6. Call execute(input, client)
22
+ * 7. Normalize and return the result (Fix 8)
23
+ */
24
+ export declare function executeToolInternal(name: string, input: Record<string, unknown>): Promise<ToolExecuteResult>;
25
+ /**
26
+ * Returns true when the browser exposes a native WebMCP implementation
27
+ * (Chrome 146+ or a compatible browser) — no polyfill needed in that case.
28
+ */
29
+ export declare function hasNativeWebMCP(): boolean;
30
+ /**
31
+ * Install the WebMCP polyfill on `navigator.modelContext`.
32
+ *
33
+ * Behavior:
34
+ * - If native WebMCP is present and `forcePolyfill` is not set, skip install
35
+ * - If already installed by us, call cleanupWebMCPPolyfill first then re-install
36
+ * - Saves previous property descriptors for restore on cleanup (Fix 7)
37
+ * - Installs testing shim per installTestingShim option (Fix 14)
38
+ *
39
+ * Throws a SecurityError if called from a non-secure context (HTTP)
40
+ * unless `options.allowInsecureContext` is true.
41
+ */
42
+ export declare function initializeWebMCPPolyfill(options?: WebMCPPolyfillOptions): void;
43
+ /**
44
+ * Remove the WebMCP polyfill from navigator and reset all module state.
45
+ *
46
+ * Restores previous property descriptors if they existed (Fix 7).
47
+ * Call this in your framework's cleanup / onDestroy hook to support
48
+ * Vite/Svelte HMR without tool duplication on reload.
49
+ */
50
+ export declare function cleanupWebMCPPolyfill(): void;
51
+ export { initializeWebMCPPolyfill as default };
package/src/types.d.ts ADDED
@@ -0,0 +1,331 @@
1
+ export type JsonSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
2
+ export interface JsonSchemaObject {
3
+ type?: JsonSchemaType | JsonSchemaType[];
4
+ title?: string;
5
+ description?: string;
6
+ default?: unknown;
7
+ examples?: unknown[];
8
+ minLength?: number;
9
+ maxLength?: number;
10
+ pattern?: string;
11
+ format?: string;
12
+ enum?: unknown[];
13
+ const?: unknown;
14
+ minimum?: number;
15
+ maximum?: number;
16
+ exclusiveMinimum?: number | boolean;
17
+ exclusiveMaximum?: number | boolean;
18
+ multipleOf?: number;
19
+ properties?: Record<string, JsonSchema>;
20
+ required?: string[];
21
+ additionalProperties?: boolean | JsonSchema;
22
+ patternProperties?: Record<string, JsonSchema>;
23
+ items?: JsonSchema | JsonSchema[];
24
+ minItems?: number;
25
+ maxItems?: number;
26
+ uniqueItems?: boolean;
27
+ allOf?: JsonSchema[];
28
+ anyOf?: JsonSchema[];
29
+ oneOf?: JsonSchema[];
30
+ not?: JsonSchema;
31
+ $ref?: string;
32
+ $schema?: string;
33
+ $id?: string;
34
+ $defs?: Record<string, JsonSchema>;
35
+ definitions?: Record<string, JsonSchema>;
36
+ if?: JsonSchema;
37
+ then?: JsonSchema;
38
+ else?: JsonSchema;
39
+ nullable?: boolean;
40
+ }
41
+ export type JsonSchema = boolean | JsonSchemaObject;
42
+ export interface ToolAnnotations {
43
+ /** Hint: tool does not modify any state */
44
+ readOnlyHint?: boolean;
45
+ /** Hint: tool may modify state in a destructive/irreversible way */
46
+ destructiveHint?: boolean;
47
+ /** Hint: calling the tool multiple times with the same args has same effect */
48
+ idempotentHint?: boolean;
49
+ /** Hint: tool may interact with external systems (network, disk, etc.) */
50
+ openWorldHint?: boolean;
51
+ /** Hint: tool should be used with caution */
52
+ cautionHint?: boolean;
53
+ /** Human-readable title (MCP SDK) */
54
+ title?: string;
55
+ }
56
+ export interface ModelContextTool {
57
+ /** Unique name, must not be empty */
58
+ name: string;
59
+ /** Human-readable description, must not be empty */
60
+ description: string;
61
+ /** JSON Schema describing the tool's input parameters */
62
+ inputSchema?: JsonSchema;
63
+ /** JSON Schema describing the tool's structured output */
64
+ outputSchema?: JsonSchema;
65
+ /** Annotations about tool behavior */
66
+ annotations?: ToolAnnotations;
67
+ }
68
+ /** Content types returned by a tool */
69
+ export type ToolContentType = 'text' | 'image' | 'resource' | 'error';
70
+ export interface ToolContentText {
71
+ type: 'text';
72
+ text: string;
73
+ }
74
+ export interface ToolContentImage {
75
+ type: 'image';
76
+ data: string;
77
+ mimeType: string;
78
+ }
79
+ export interface ToolContentResource {
80
+ type: 'resource';
81
+ resource: {
82
+ uri: string;
83
+ mimeType?: string;
84
+ text?: string;
85
+ blob?: string;
86
+ };
87
+ }
88
+ export interface ToolContentError {
89
+ type: 'error';
90
+ error: string;
91
+ }
92
+ export type ToolContent = ToolContentText | ToolContentImage | ToolContentResource | ToolContentError;
93
+ /** Extensible metadata for tool results */
94
+ export interface ToolResultMetadata {
95
+ willNavigate?: boolean;
96
+ [key: string]: unknown;
97
+ }
98
+ export interface ToolExecuteResult {
99
+ content: ToolContent[];
100
+ isError?: boolean;
101
+ /** JSON-serializable structured data (MCP structured output) */
102
+ structuredContent?: Record<string, unknown>;
103
+ /** Extensible metadata */
104
+ _meta?: ToolResultMetadata;
105
+ }
106
+ export interface UserInteractionOptions {
107
+ /** Human-readable prompt to show the user */
108
+ prompt?: string;
109
+ /** Interaction type hint */
110
+ kind?: 'confirmation' | 'input' | 'choice';
111
+ /** For 'choice' kind */
112
+ choices?: string[];
113
+ }
114
+ export interface UserInteractionResult {
115
+ accepted: boolean;
116
+ value?: string;
117
+ }
118
+ export interface ModelContextClient {
119
+ /**
120
+ * Request user interaction before the tool proceeds.
121
+ * W3C spec marks this as TODO — placeholder implementation.
122
+ */
123
+ requestUserInteraction(options?: UserInteractionOptions): Promise<UserInteractionResult>;
124
+ }
125
+ export type ToolExecuteCallback = (input: Record<string, unknown>, client: ModelContextClient) => Promise<ToolExecuteResult> | ToolExecuteResult;
126
+ export interface ToolRegistrationOptions {
127
+ /** AbortSignal to auto-unregister the tool when aborted */
128
+ signal?: AbortSignal;
129
+ }
130
+ export interface RegisteredTool {
131
+ tool: ModelContextTool;
132
+ execute: ToolExecuteCallback;
133
+ /** JSON.stringify'd input schema for fast serialization */
134
+ serializedInputSchema?: string;
135
+ registeredAt: number;
136
+ }
137
+ export interface ContextRegistration {
138
+ tool: ModelContextTool & {
139
+ execute: ToolExecuteCallback;
140
+ };
141
+ options?: ToolRegistrationOptions;
142
+ }
143
+ /** Called with no arguments when the tool set changes (MCP-B spec). */
144
+ export type ToolsChangedCallback = () => void;
145
+ export interface ModelContext {
146
+ registerTool(tool: ModelContextTool & {
147
+ execute: ToolExecuteCallback;
148
+ }, options?: ToolRegistrationOptions): void;
149
+ unregisterTool(nameOrTool: string | {
150
+ name: string;
151
+ }): void;
152
+ provideContext(registrations: ContextRegistration[]): void;
153
+ clearContext(): void;
154
+ registerToolsChangedCallback(callback: ToolsChangedCallback): void;
155
+ }
156
+ /** Info shape returned by modelContextTesting.listTools() */
157
+ export interface ModelContextTestingToolInfo {
158
+ name: string;
159
+ description: string;
160
+ /** Serialized JSON Schema string */
161
+ inputSchema?: string;
162
+ }
163
+ export interface ModelContextTesting {
164
+ listTools(): ModelContextTestingToolInfo[];
165
+ executeTool(toolName: string, inputArgsJson: string, options?: {
166
+ signal?: AbortSignal;
167
+ }): Promise<string | null>;
168
+ getCrossDocumentScriptToolResult(): Promise<string>;
169
+ }
170
+ export interface AuthContext {
171
+ userId?: string;
172
+ tenantId?: string;
173
+ workspaceId?: string;
174
+ roles?: string[];
175
+ [key: string]: unknown;
176
+ }
177
+ export type ConfirmationTier = 'none' | 'warn' | 'block';
178
+ export interface ConfirmationPolicy {
179
+ /** Require confirmation for destructive tools */
180
+ destructive?: ConfirmationTier;
181
+ /** Require confirmation for tools without readOnlyHint */
182
+ mutating?: ConfirmationTier;
183
+ /** Require confirmation for all tools */
184
+ all?: ConfirmationTier;
185
+ }
186
+ export interface WebMCPPolyfillOptions {
187
+ /**
188
+ * Force installing the polyfill even when native WebMCP is detected.
189
+ * Useful for testing.
190
+ */
191
+ forcePolyfill?: boolean;
192
+ /**
193
+ * Allow running in non-secure contexts (HTTP).
194
+ * Should only be set to true during development.
195
+ */
196
+ allowInsecureContext?: boolean;
197
+ /**
198
+ * If true and the environment has no window (SSR/Node),
199
+ * all polyfill functions become silent no-ops.
200
+ */
201
+ degradeGracefully?: boolean;
202
+ /**
203
+ * Auth context injected into every tool's input as `_authContext`.
204
+ */
205
+ authContext?: AuthContext;
206
+ /**
207
+ * Confirmation policy applied before executing tools.
208
+ */
209
+ confirmationPolicy?: ConfirmationPolicy;
210
+ /**
211
+ * Controls when the testing shim (navigator.modelContextTesting) is installed.
212
+ * - 'if-missing' (default): install only if navigator.modelContextTesting is not already set
213
+ * - 'always': always install (overwrite native)
214
+ * - false: never install
215
+ */
216
+ installTestingShim?: 'if-missing' | 'always' | false;
217
+ /**
218
+ * If false, skip IIFE auto-initialization even when window.__webMCPPolyfillOptions is set.
219
+ * Default: true
220
+ */
221
+ autoInitialize?: boolean;
222
+ }
223
+ export interface JsonRpcRequest {
224
+ jsonrpc: '2.0';
225
+ id: number | string;
226
+ method: string;
227
+ params?: Record<string, unknown>;
228
+ }
229
+ export interface JsonRpcError {
230
+ code: number;
231
+ message: string;
232
+ data?: unknown;
233
+ }
234
+ export interface JsonRpcResponse<T = unknown> {
235
+ jsonrpc: '2.0';
236
+ id: number | string | null;
237
+ result?: T;
238
+ error?: JsonRpcError;
239
+ }
240
+ export interface McpServerInfo {
241
+ name: string;
242
+ version: string;
243
+ }
244
+ export interface McpCapabilities {
245
+ tools?: {
246
+ listChanged?: boolean;
247
+ };
248
+ resources?: {
249
+ listChanged?: boolean;
250
+ subscribe?: boolean;
251
+ };
252
+ prompts?: {
253
+ listChanged?: boolean;
254
+ };
255
+ logging?: Record<string, unknown>;
256
+ [key: string]: unknown;
257
+ }
258
+ export interface McpInitializeResult {
259
+ protocolVersion: string;
260
+ capabilities: McpCapabilities;
261
+ serverInfo: McpServerInfo;
262
+ }
263
+ export interface McpTool {
264
+ name: string;
265
+ description?: string;
266
+ inputSchema?: JsonSchema;
267
+ outputSchema?: JsonSchema;
268
+ annotations?: ToolAnnotations;
269
+ }
270
+ export interface McpToolResultContent {
271
+ type: 'text' | 'image' | 'resource';
272
+ text?: string;
273
+ data?: string;
274
+ mimeType?: string;
275
+ resource?: {
276
+ uri: string;
277
+ mimeType?: string;
278
+ text?: string;
279
+ blob?: string;
280
+ };
281
+ }
282
+ export interface McpToolResult {
283
+ content: McpToolResultContent[];
284
+ isError?: boolean;
285
+ }
286
+ export interface McpListToolsResult {
287
+ tools: McpTool[];
288
+ nextCursor?: string;
289
+ }
290
+ export interface McpClientOptions {
291
+ /** Client name sent during initialize */
292
+ clientName?: string;
293
+ /** Client version sent during initialize */
294
+ clientVersion?: string;
295
+ /** Request timeout in ms (default: 30000) */
296
+ timeout?: number;
297
+ /** Extra headers attached to every request */
298
+ headers?: Record<string, string>;
299
+ /** Auto-reconnect on 404 session-expired */
300
+ autoReconnect?: boolean;
301
+ /** Max reconnect attempts (default: 3) */
302
+ maxReconnectAttempts?: number;
303
+ }
304
+ export interface PostMessageBridgeOptions {
305
+ /** Target origin for postMessage calls (default: '*') */
306
+ targetOrigin?: string;
307
+ /** Origins to accept messages from; empty array = accept all */
308
+ allowedOrigins?: string[];
309
+ }
310
+ export interface WebMCPCallToolEvent {
311
+ type: 'webmcp:call-tool';
312
+ callId: string;
313
+ name: string;
314
+ args?: Record<string, unknown>;
315
+ }
316
+ export interface WebMCPToolResultEvent {
317
+ type: 'webmcp:tool-result';
318
+ callId: string;
319
+ result: ToolExecuteResult;
320
+ }
321
+ export interface WebMCPToolErrorEvent {
322
+ type: 'webmcp:tool-error';
323
+ callId: string;
324
+ error: string;
325
+ }
326
+ declare global {
327
+ interface Navigator {
328
+ modelContext?: ModelContext;
329
+ modelContextTesting?: ModelContextTesting;
330
+ }
331
+ }
package/src/utils.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import type { JsonSchema } from './types.js';
2
+ /**
3
+ * Dispatch a CustomEvent and wait for a completion event.
4
+ * Solves the "execute must return after UI updates" requirement.
5
+ *
6
+ * Pattern: dispatch "tool-action" → UI handles it → UI dispatches "tool-completion-{requestId}"
7
+ *
8
+ * @param eventName - Name of the event to dispatch
9
+ * @param detail - Event detail payload
10
+ * @param options - Timeout and target
11
+ * @returns Promise that resolves when the completion event fires
12
+ */
13
+ export declare function dispatchAndWait<T = unknown>(eventName: string, detail?: Record<string, unknown>, options?: {
14
+ timeout?: number;
15
+ successValue?: T;
16
+ target?: EventTarget;
17
+ }): Promise<T>;
18
+ /**
19
+ * Helper to signal completion of a dispatchAndWait request.
20
+ * Call this from your UI handler after the update is done.
21
+ */
22
+ export declare function signalCompletion(completionEventName: string, detail?: unknown): void;
23
+ /**
24
+ * Strip JSON Schema keywords that cause errors in AI SDKs
25
+ * (e.g., Vercel AI SDK rejects oneOf/anyOf on non-STRING types).
26
+ *
27
+ * Use this before passing tool schemas to LLM providers.
28
+ */
29
+ export declare function sanitizeSchema(schema: JsonSchema): JsonSchema;
30
+ /**
31
+ * Create a named tool group backed by a shared AbortController.
32
+ * Ergonomic helper for SPA route-based tool registration.
33
+ *
34
+ * Usage:
35
+ * const group = createToolGroup('search-page');
36
+ * group.register(tool1, registerFn);
37
+ * group.register(tool2, registerFn);
38
+ * // On route change:
39
+ * group.abort(); // unregisters all tools in the group
40
+ */
41
+ export declare function createToolGroup(name: string): {
42
+ name: string;
43
+ register: (tool: {
44
+ name: string;
45
+ description: string;
46
+ inputSchema?: JsonSchema;
47
+ execute: (...args: any[]) => any;
48
+ annotations?: Record<string, unknown>;
49
+ }, registerFn: (tool: any, options: {
50
+ signal: AbortSignal;
51
+ }) => void) => void;
52
+ abort: () => void;
53
+ isAborted: () => boolean;
54
+ };
@@ -0,0 +1,13 @@
1
+ import type { JsonSchema } from './types.js';
2
+ export interface ValidationError {
3
+ path: string;
4
+ message: string;
5
+ keyword: string;
6
+ expected?: unknown;
7
+ actual?: unknown;
8
+ }
9
+ export interface ValidationResult {
10
+ valid: boolean;
11
+ errors: ValidationError[];
12
+ }
13
+ export declare function validateJsonSchema(value: unknown, schema: JsonSchema, path?: string): ValidationResult;
@@ -0,0 +1,3 @@
1
+ import type { ToolExecuteResult } from './types.js';
2
+ export declare function textResult(text: string): ToolExecuteResult;
3
+ export declare function jsonResult(data: unknown): ToolExecuteResult;
@@ -1,11 +1,14 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // @webmcp-auto-ui/core — WebMCP helpers
3
- // Thin layer on top of polyfill.ts: skill registry + result builders.
3
+ // Result builders for tool execute callbacks.
4
4
  // Zero additional dependencies. SSR-safe.
5
+ //
6
+ // NOTE: The skill registry that used to live here (SkillDef, registerSkill,
7
+ // unregisterSkill, etc.) has been removed. The canonical skill type and
8
+ // registry now live in @webmcp-auto-ui/sdk (packages/sdk/src/skills/registry.ts).
5
9
  // ---------------------------------------------------------------------------
6
10
 
7
- import { executeToolInternal } from './polyfill.js';
8
- import type { ModelContextTool, ToolExecuteCallback, ToolExecuteResult } from './types.js';
11
+ import type { ToolExecuteResult } from './types.js';
9
12
 
10
13
  // ---------------------------------------------------------------------------
11
14
  // Result builders
@@ -18,63 +21,3 @@ export function textResult(text: string): ToolExecuteResult {
18
21
  export function jsonResult(data: unknown): ToolExecuteResult {
19
22
  return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
20
23
  }
21
-
22
- // ---------------------------------------------------------------------------
23
- // Skill registry
24
- // ---------------------------------------------------------------------------
25
-
26
- export interface SkillDef {
27
- id: string;
28
- name: string;
29
- description: string;
30
- component: string;
31
- presentation?: string;
32
- version?: string;
33
- tags?: string[];
34
- }
35
-
36
- const _skills = new Map<string, SkillDef>();
37
-
38
- function mcNav(): {
39
- registerTool: (t: ModelContextTool & { execute: ToolExecuteCallback }) => void;
40
- unregisterTool: (name: string) => void;
41
- } | null {
42
- if (typeof navigator === 'undefined') return null;
43
- return (navigator as unknown as Record<string, unknown>).modelContext as ReturnType<typeof mcNav> ?? null;
44
- }
45
-
46
- export function registerSkill(skill: SkillDef): void {
47
- _skills.set(skill.id, skill);
48
- const mc = mcNav();
49
- if (!mc) return;
50
- const toolName = `skill__${skill.id}`;
51
- try { mc.unregisterTool(toolName); } catch { /* not yet registered */ }
52
- mc.registerTool({
53
- name: toolName,
54
- description: `[Skill] ${skill.name} — ${skill.description}. Component: ${skill.component}.${skill.presentation ? ` Hints: ${skill.presentation}` : ''}`,
55
- inputSchema: { type: 'object', properties: {} },
56
- execute: () => jsonResult(skill),
57
- annotations: { readOnlyHint: true },
58
- });
59
- }
60
-
61
- export function unregisterSkill(id: string): void {
62
- _skills.delete(id);
63
- const mc = mcNav();
64
- if (!mc) return;
65
- try { mc.unregisterTool(`skill__${id}`); } catch { /* already gone */ }
66
- }
67
-
68
- export function getSkill(id: string): SkillDef | undefined {
69
- return _skills.get(id);
70
- }
71
-
72
- export function listSkills(): SkillDef[] {
73
- return Array.from(_skills.values());
74
- }
75
-
76
- export function clearSkills(): void {
77
- for (const id of Array.from(_skills.keys())) unregisterSkill(id);
78
- }
79
-
80
- export { executeToolInternal };
@@ -0,0 +1,53 @@
1
+ export interface WebMcpServerOptions {
2
+ description: string;
3
+ }
4
+ export interface WebMcpToolDef {
5
+ name: string;
6
+ description: string;
7
+ inputSchema: Record<string, unknown>;
8
+ execute: (params: Record<string, unknown>) => Promise<unknown>;
9
+ }
10
+ /** A vanilla renderer: receives a container + data, optionally returns a cleanup function. */
11
+ export type WidgetRenderer = ((container: HTMLElement, data: Record<string, unknown>) => void | (() => void)) | unknown;
12
+ export interface WidgetEntry {
13
+ name: string;
14
+ description: string;
15
+ inputSchema: Record<string, unknown>;
16
+ recipe: string;
17
+ renderer: WidgetRenderer;
18
+ group?: string;
19
+ /** True when the renderer is a plain function (not a framework component). */
20
+ vanilla: boolean;
21
+ }
22
+ export interface WebMcpServer {
23
+ readonly name: string;
24
+ readonly description: string;
25
+ registerWidget(recipeMarkdown: string, renderer: WidgetRenderer): void;
26
+ addTool(tool: WebMcpToolDef): void;
27
+ layer(): {
28
+ protocol: 'webmcp';
29
+ serverName: string;
30
+ description: string;
31
+ tools: WebMcpToolDef[];
32
+ };
33
+ getWidget(name: string): WidgetEntry | undefined;
34
+ listWidgets(): WidgetEntry[];
35
+ }
36
+ export interface ParsedFrontmatter {
37
+ frontmatter: Record<string, unknown>;
38
+ body: string;
39
+ }
40
+ /**
41
+ * Parse a markdown file with YAML frontmatter (--- delimited).
42
+ * Supports: scalars, nested objects (indentation), arrays (- item), inline values.
43
+ * No external YAML dependency.
44
+ */
45
+ export declare function parseFrontmatter(markdown: string): ParsedFrontmatter;
46
+ /**
47
+ * Mount a widget into a DOM container by searching registered servers.
48
+ * If the renderer is a function (vanilla renderer), it is called directly.
49
+ * Returns an optional cleanup function.
50
+ * Falls back to a text placeholder if no server provides the widget.
51
+ */
52
+ export declare function mountWidget(container: HTMLElement, type: string, data: Record<string, unknown>, servers: WebMcpServer[]): (() => void) | void;
53
+ export declare function createWebMcpServer(name: string, options: WebMcpServerOptions): WebMcpServer;