@docyrus/docyrus 0.0.19 → 0.0.21
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/agent-loader.js +37 -3
- package/agent-loader.js.map +2 -2
- package/main.js +498 -93
- package/main.js.map +4 -4
- package/package.json +14 -4
- package/resources/chrome-tools/browser-content.js +103 -0
- package/resources/chrome-tools/browser-cookies.js +35 -0
- package/resources/chrome-tools/browser-eval.js +53 -0
- package/resources/chrome-tools/browser-hn-scraper.js +108 -0
- package/resources/chrome-tools/browser-nav.js +44 -0
- package/resources/chrome-tools/browser-pick.js +162 -0
- package/resources/chrome-tools/browser-screenshot.js +34 -0
- package/resources/chrome-tools/browser-start.js +86 -0
- package/resources/pi-agent/extensions/answer.ts +532 -0
- package/resources/pi-agent/extensions/context.ts +578 -0
- package/resources/pi-agent/extensions/control.ts +1779 -0
- package/resources/pi-agent/extensions/diff.ts +218 -0
- package/resources/pi-agent/extensions/files.ts +199 -0
- package/resources/pi-agent/extensions/loop.ts +446 -0
- package/resources/pi-agent/extensions/multi-edit.ts +835 -0
- package/resources/pi-agent/extensions/notify.ts +88 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/CHANGELOG.md +192 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/LICENSE +21 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/README.md +296 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/app-bridge.bundle.js +67 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/cli.js +108 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/commands.ts +211 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/config.ts +227 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/consent-manager.ts +64 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/direct-tools.ts +301 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/errors.ts +219 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/glimpse-ui.ts +80 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/host-html-template.ts +427 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/index.ts +232 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/init.ts +319 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/lifecycle.ts +93 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/logger.ts +169 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/mcp-panel.ts +713 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/metadata-cache.ts +191 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/npx-resolver.ts +419 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/oauth-handler.ts +56 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/package.json +85 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/paths.ts +29 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/proxy-modes.ts +635 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/resource-tools.ts +17 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/server-manager.ts +330 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/state.ts +41 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/tool-metadata.ts +144 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/tool-registrar.ts +46 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/types.ts +367 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/ui-resource-handler.ts +145 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/ui-server.ts +623 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/ui-session.ts +384 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/ui-stream-types.ts +89 -0
- package/resources/pi-agent/extensions/pi-mcp-adapter/utils.ts +75 -0
- package/resources/pi-agent/extensions/prompt-editor.ts +1315 -0
- package/resources/pi-agent/extensions/prompt-url-widget.ts +158 -0
- package/resources/pi-agent/extensions/redraws.ts +24 -0
- package/resources/pi-agent/extensions/review.ts +2160 -0
- package/resources/pi-agent/extensions/todos.ts +2076 -0
- package/resources/pi-agent/extensions/tps.ts +47 -0
- package/resources/pi-agent/extensions/whimsical.ts +474 -0
- package/resources/pi-agent/prompts/coder-system.md +106 -0
- package/resources/pi-agent/skills/changelog-generator/SKILL.md +425 -0
- package/resources/pi-agent/skills/docyrus-chrome-devtools-cli/SKILL.md +80 -0
- package/resources/pi-agent/skills/docyrus-platform/SKILL.md +71 -0
- package/resources/pi-agent/skills/docyrus-platform/references/ai-capabilities.md +43 -0
- package/resources/pi-agent/skills/docyrus-platform/references/auth-and-multi-tenancy.md +35 -0
- package/resources/pi-agent/skills/docyrus-platform/references/automation-and-workflows.md +30 -0
- package/resources/pi-agent/skills/docyrus-platform/references/core-building-blocks.md +53 -0
- package/resources/pi-agent/skills/{docyrus-api-dev → docyrus-platform}/references/data-source-query-guide.md +32 -28
- package/resources/pi-agent/skills/docyrus-platform/references/developer-tools.md +28 -0
- package/resources/pi-agent/skills/docyrus-platform/references/docyrus-cli-usage.md +554 -0
- package/resources/pi-agent/skills/{docyrus-api-dev → docyrus-platform}/references/formula-design-guide-llm.md +15 -23
- package/resources/pi-agent/skills/docyrus-platform/references/integrations-and-events.md +60 -0
- package/resources/pi-agent/skills/docyrus-platform/references/platform-services.md +58 -0
- package/resources/pi-agent/skills/docyrus-platform/references/querying-and-data-operations.md +27 -0
- package/resources/pi-agent/prompts/coder-append-system.md +0 -19
- package/resources/pi-agent/skills/docyrus-ai/SKILL.md +0 -28
- package/resources/pi-agent/skills/docyrus-api-dev/SKILL.md +0 -161
- package/resources/pi-agent/skills/docyrus-api-dev/references/api-client.md +0 -349
- package/resources/pi-agent/skills/docyrus-api-dev/references/authentication.md +0 -238
- package/resources/pi-agent/skills/docyrus-api-dev/references/query-and-formulas.md +0 -592
- package/resources/pi-agent/skills/docyrus-api-doctor/SKILL.md +0 -70
- package/resources/pi-agent/skills/docyrus-api-doctor/references/checklist-details.md +0 -588
- package/resources/pi-agent/skills/docyrus-app-dev/SKILL.md +0 -159
- package/resources/pi-agent/skills/docyrus-app-dev/references/api-client-and-auth.md +0 -275
- package/resources/pi-agent/skills/docyrus-app-dev/references/collections-and-patterns.md +0 -352
- package/resources/pi-agent/skills/docyrus-app-dev/references/data-source-query-guide.md +0 -2059
- package/resources/pi-agent/skills/docyrus-app-dev/references/formula-design-guide-llm.md +0 -320
- package/resources/pi-agent/skills/docyrus-app-dev/references/query-guide.md +0 -525
- package/resources/pi-agent/skills/docyrus-app-ui-design/SKILL.md +0 -466
- package/resources/pi-agent/skills/docyrus-app-ui-design/references/component-selection-guide.md +0 -602
- package/resources/pi-agent/skills/docyrus-app-ui-design/references/icon-usage-guide.md +0 -463
- package/resources/pi-agent/skills/docyrus-app-ui-design/references/preferred-components-catalog.md +0 -242
- package/resources/pi-agent/skills/docyrus-apps/SKILL.md +0 -54
- package/resources/pi-agent/skills/docyrus-architect/SKILL.md +0 -174
- package/resources/pi-agent/skills/docyrus-architect/references/custom-query-guide.md +0 -410
- package/resources/pi-agent/skills/docyrus-architect/references/data-source-query-guide.md +0 -2059
- package/resources/pi-agent/skills/docyrus-architect/references/formula-design-guide-llm.md +0 -320
- package/resources/pi-agent/skills/docyrus-architect/references/formula-reference.md +0 -145
- package/resources/pi-agent/skills/docyrus-auth/SKILL.md +0 -100
- package/resources/pi-agent/skills/docyrus-cli-app/SKILL.md +0 -279
- package/resources/pi-agent/skills/docyrus-cli-app/references/cli-manifest.md +0 -532
- package/resources/pi-agent/skills/docyrus-cli-app/references/list-query-examples.md +0 -248
- package/resources/pi-agent/skills/docyrus-curl/SKILL.md +0 -32
- package/resources/pi-agent/skills/docyrus-discover/SKILL.md +0 -63
- package/resources/pi-agent/skills/docyrus-ds/SKILL.md +0 -95
- package/resources/pi-agent/skills/docyrus-env/SKILL.md +0 -21
- package/resources/pi-agent/skills/docyrus-studio/SKILL.md +0 -369
- package/resources/pi-agent/skills/docyrus-tui/SKILL.md +0 -15
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
// types.ts - Core type definitions
|
|
2
|
+
import type { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import type { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
4
|
+
import type { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
5
|
+
import type { TextContent, ImageContent } from "@mariozechner/pi-ai";
|
|
6
|
+
|
|
7
|
+
// Transport type (stdio + HTTP)
|
|
8
|
+
export type Transport =
|
|
9
|
+
| StdioClientTransport
|
|
10
|
+
| SSEClientTransport
|
|
11
|
+
| StreamableHTTPClientTransport;
|
|
12
|
+
|
|
13
|
+
// Import sources for config
|
|
14
|
+
export type ImportKind =
|
|
15
|
+
| "cursor"
|
|
16
|
+
| "claude-code"
|
|
17
|
+
| "claude-desktop"
|
|
18
|
+
| "codex"
|
|
19
|
+
| "windsurf"
|
|
20
|
+
| "vscode";
|
|
21
|
+
|
|
22
|
+
// Tool definition from MCP server
|
|
23
|
+
export interface McpTool {
|
|
24
|
+
name: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
inputSchema?: unknown; // JSON Schema
|
|
28
|
+
_meta?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Resource definition from MCP server
|
|
32
|
+
export interface McpResource {
|
|
33
|
+
uri: string;
|
|
34
|
+
name: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
mimeType?: string;
|
|
37
|
+
_meta?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface UiResourceMeta {
|
|
41
|
+
csp?: UiResourceCsp;
|
|
42
|
+
permissions?: UiResourcePermissions;
|
|
43
|
+
domain?: string;
|
|
44
|
+
prefersBorder?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface UiResourceContent {
|
|
48
|
+
uri: string;
|
|
49
|
+
html: string;
|
|
50
|
+
mimeType?: string;
|
|
51
|
+
meta: UiResourceMeta;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface UiProxyRequestBody<TParams> {
|
|
55
|
+
token: string;
|
|
56
|
+
params: TParams;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface UiProxyResult<T = Record<string, unknown>> {
|
|
60
|
+
ok: boolean;
|
|
61
|
+
result?: T;
|
|
62
|
+
error?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface UiResourceCsp {
|
|
66
|
+
connectDomains?: string[];
|
|
67
|
+
scriptDomains?: string[];
|
|
68
|
+
styleDomains?: string[];
|
|
69
|
+
fontDomains?: string[];
|
|
70
|
+
imgDomains?: string[];
|
|
71
|
+
mediaDomains?: string[];
|
|
72
|
+
frameDomains?: string[];
|
|
73
|
+
workerDomains?: string[];
|
|
74
|
+
baseUriDomains?: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface UiResourcePermissions {
|
|
78
|
+
camera?: {};
|
|
79
|
+
microphone?: {};
|
|
80
|
+
geolocation?: {};
|
|
81
|
+
clipboardWrite?: {};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface UiToolInfo {
|
|
85
|
+
id?: string | number;
|
|
86
|
+
tool: {
|
|
87
|
+
name: string;
|
|
88
|
+
description?: string;
|
|
89
|
+
inputSchema?: unknown;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface UiHostContext {
|
|
94
|
+
toolInfo?: UiToolInfo;
|
|
95
|
+
theme?: "light" | "dark";
|
|
96
|
+
styles?: Record<string, unknown>;
|
|
97
|
+
displayMode?: UiDisplayMode;
|
|
98
|
+
availableDisplayModes?: UiDisplayMode[];
|
|
99
|
+
containerDimensions?: {
|
|
100
|
+
width?: number;
|
|
101
|
+
maxWidth?: number;
|
|
102
|
+
height?: number;
|
|
103
|
+
maxHeight?: number;
|
|
104
|
+
};
|
|
105
|
+
[key: string]: unknown;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type UiDisplayMode = "inline" | "fullscreen" | "pip";
|
|
109
|
+
|
|
110
|
+
// Re-export stream types from the shared lightweight module.
|
|
111
|
+
// This allows the example package to import stream schemas without pulling the full types.ts dependency graph.
|
|
112
|
+
export {
|
|
113
|
+
UI_STREAM_HOST_CONTEXT_KEY,
|
|
114
|
+
UI_STREAM_REQUEST_META_KEY,
|
|
115
|
+
UI_STREAM_RESULT_PATCH_METHOD,
|
|
116
|
+
SERVER_STREAM_RESULT_PATCH_METHOD,
|
|
117
|
+
UI_STREAM_STRUCTURED_CONTENT_KEY,
|
|
118
|
+
uiStreamModeSchema,
|
|
119
|
+
visualizationStreamPhaseSchema,
|
|
120
|
+
visualizationStreamFrameTypeSchema,
|
|
121
|
+
visualizationStreamStatusSchema,
|
|
122
|
+
uiStreamHostContextSchema,
|
|
123
|
+
visualizationStreamEnvelopeSchema,
|
|
124
|
+
uiStreamCallToolResultSchema,
|
|
125
|
+
uiStreamResultPatchNotificationSchema,
|
|
126
|
+
serverStreamResultPatchNotificationSchema,
|
|
127
|
+
getUiStreamHostContext,
|
|
128
|
+
getVisualizationStreamEnvelope,
|
|
129
|
+
type UiStreamMode,
|
|
130
|
+
type VisualizationStreamPhase,
|
|
131
|
+
type VisualizationStreamFrameType,
|
|
132
|
+
type VisualizationStreamStatus,
|
|
133
|
+
type UiStreamHostContext,
|
|
134
|
+
type VisualizationStreamEnvelope,
|
|
135
|
+
type UiStreamCallToolResult,
|
|
136
|
+
type UiStreamResultPatchNotification,
|
|
137
|
+
type ServerStreamResultPatchNotification,
|
|
138
|
+
type UiStreamSummary,
|
|
139
|
+
} from "./ui-stream-types.js";
|
|
140
|
+
|
|
141
|
+
export interface UiMessageParams {
|
|
142
|
+
role?: string;
|
|
143
|
+
content?: unknown[];
|
|
144
|
+
type?: "prompt" | "notify" | "intent" | "message";
|
|
145
|
+
message?: string;
|
|
146
|
+
prompt?: string;
|
|
147
|
+
intent?: string;
|
|
148
|
+
params?: Record<string, unknown>;
|
|
149
|
+
[key: string]: unknown;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Extract prompt text from either legacy MCP UI message shapes or native AppBridge user messages.
|
|
154
|
+
*/
|
|
155
|
+
export function extractUiPromptText(params: UiMessageParams): string | undefined {
|
|
156
|
+
if (params.type === "prompt" || params.prompt) {
|
|
157
|
+
const prompt = params.prompt ?? String(params.message ?? "");
|
|
158
|
+
return prompt || undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (params.role === "user" && Array.isArray(params.content)) {
|
|
162
|
+
const text = params.content
|
|
163
|
+
.map((block) => (block && typeof block === "object" && "text" in block ? String((block as { text?: unknown }).text ?? "") : ""))
|
|
164
|
+
.filter(Boolean)
|
|
165
|
+
.join("\n\n");
|
|
166
|
+
return text || undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Structured UI handoff recovered from a canonical prompt envelope.
|
|
174
|
+
*/
|
|
175
|
+
export interface UiPromptHandoff {
|
|
176
|
+
intent: string;
|
|
177
|
+
params: Record<string, unknown>;
|
|
178
|
+
raw: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Parse a canonical named UI handoff encoded as `intent\n{json}`.
|
|
183
|
+
*/
|
|
184
|
+
export function parseUiPromptHandoff(prompt: string): UiPromptHandoff | undefined {
|
|
185
|
+
const newlineIndex = prompt.indexOf("\n");
|
|
186
|
+
if (newlineIndex <= 0) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const intent = prompt.slice(0, newlineIndex).trim();
|
|
191
|
+
const payloadText = prompt.slice(newlineIndex + 1).trim();
|
|
192
|
+
if (!intent || !payloadText) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(intent)) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const parsed = JSON.parse(payloadText);
|
|
202
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
intent,
|
|
207
|
+
params: parsed as Record<string, unknown>,
|
|
208
|
+
raw: prompt,
|
|
209
|
+
};
|
|
210
|
+
} catch {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Accumulated messages from a UI session.
|
|
217
|
+
* Collected during the session and available when it ends.
|
|
218
|
+
*/
|
|
219
|
+
export interface UiSessionMessages {
|
|
220
|
+
prompts: string[];
|
|
221
|
+
notifications: string[];
|
|
222
|
+
intents: Array<{ intent: string; params?: Record<string, unknown> }>;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface UiModelContextParams {
|
|
226
|
+
content?: unknown[];
|
|
227
|
+
structuredContent?: Record<string, unknown>;
|
|
228
|
+
[key: string]: unknown;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface UiOpenLinkResult {
|
|
232
|
+
isError?: boolean;
|
|
233
|
+
[key: string]: unknown;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface UiDisplayModeRequest {
|
|
237
|
+
mode?: UiDisplayMode;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface UiDisplayModeResult {
|
|
241
|
+
mode: UiDisplayMode;
|
|
242
|
+
[key: string]: unknown;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Content types from MCP
|
|
246
|
+
export interface McpContent {
|
|
247
|
+
type: "text" | "image" | "audio" | "resource" | "resource_link";
|
|
248
|
+
text?: string;
|
|
249
|
+
data?: string;
|
|
250
|
+
mimeType?: string;
|
|
251
|
+
resource?: {
|
|
252
|
+
uri: string;
|
|
253
|
+
text?: string;
|
|
254
|
+
blob?: string;
|
|
255
|
+
};
|
|
256
|
+
uri?: string;
|
|
257
|
+
name?: string;
|
|
258
|
+
description?: string;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Pi content block type
|
|
262
|
+
export type ContentBlock = TextContent | ImageContent;
|
|
263
|
+
|
|
264
|
+
// Server configuration
|
|
265
|
+
export interface ServerEntry {
|
|
266
|
+
command?: string;
|
|
267
|
+
args?: string[];
|
|
268
|
+
env?: Record<string, string>;
|
|
269
|
+
cwd?: string;
|
|
270
|
+
// HTTP fields
|
|
271
|
+
url?: string;
|
|
272
|
+
headers?: Record<string, string>;
|
|
273
|
+
auth?: "oauth" | "bearer";
|
|
274
|
+
bearerToken?: string;
|
|
275
|
+
bearerTokenEnv?: string;
|
|
276
|
+
lifecycle?: "keep-alive" | "lazy" | "eager";
|
|
277
|
+
idleTimeout?: number; // minutes, overrides global setting
|
|
278
|
+
// Resource handling
|
|
279
|
+
exposeResources?: boolean;
|
|
280
|
+
// Direct tool registration
|
|
281
|
+
directTools?: boolean | string[];
|
|
282
|
+
// Debug
|
|
283
|
+
debug?: boolean; // Show server stderr (default: false)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Settings
|
|
287
|
+
export interface McpSettings {
|
|
288
|
+
toolPrefix?: "server" | "none" | "short";
|
|
289
|
+
idleTimeout?: number; // minutes, default 10, 0 to disable
|
|
290
|
+
directTools?: boolean;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Root config
|
|
294
|
+
export interface McpConfig {
|
|
295
|
+
mcpServers: Record<string, ServerEntry>;
|
|
296
|
+
imports?: ImportKind[];
|
|
297
|
+
settings?: McpSettings;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Alias for clarity
|
|
301
|
+
export type ServerDefinition = ServerEntry;
|
|
302
|
+
|
|
303
|
+
export interface ToolMetadata {
|
|
304
|
+
name: string; // Prefixed tool name (e.g., "xcodebuild_list_sims")
|
|
305
|
+
originalName: string; // Original MCP tool name (e.g., "list_sims")
|
|
306
|
+
description: string;
|
|
307
|
+
resourceUri?: string; // For resource tools: the URI to read
|
|
308
|
+
uiResourceUri?: string; // For app-enabled tools: the UI resource URI
|
|
309
|
+
inputSchema?: unknown; // JSON Schema for parameters (stored for describe/errors)
|
|
310
|
+
uiStreamMode?: UiStreamMode;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export interface DirectToolSpec {
|
|
314
|
+
serverName: string;
|
|
315
|
+
originalName: string;
|
|
316
|
+
prefixedName: string;
|
|
317
|
+
description: string;
|
|
318
|
+
inputSchema?: unknown;
|
|
319
|
+
resourceUri?: string;
|
|
320
|
+
uiResourceUri?: string;
|
|
321
|
+
uiStreamMode?: UiStreamMode;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export interface ServerProvenance {
|
|
325
|
+
path: string;
|
|
326
|
+
kind: "user" | "project" | "import";
|
|
327
|
+
importKind?: string;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export interface McpPanelCallbacks {
|
|
331
|
+
reconnect: (serverName: string) => Promise<boolean>;
|
|
332
|
+
getConnectionStatus: (serverName: string) => "connected" | "idle" | "failed" | "needs-auth";
|
|
333
|
+
refreshCacheAfterReconnect: (serverName: string) => import("./metadata-cache.js").ServerCacheEntry | null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export interface McpPanelResult {
|
|
337
|
+
changes: Map<string, true | string[] | false>;
|
|
338
|
+
cancelled: boolean;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get server prefix based on tool prefix mode.
|
|
343
|
+
*/
|
|
344
|
+
export function getServerPrefix(
|
|
345
|
+
serverName: string,
|
|
346
|
+
mode: "server" | "none" | "short"
|
|
347
|
+
): string {
|
|
348
|
+
if (mode === "none") return "";
|
|
349
|
+
if (mode === "short") {
|
|
350
|
+
let short = serverName.replace(/-?mcp$/i, "").replace(/-/g, "_");
|
|
351
|
+
if (!short) short = "mcp";
|
|
352
|
+
return short;
|
|
353
|
+
}
|
|
354
|
+
return serverName.replace(/-/g, "_");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Format a tool name with server prefix.
|
|
359
|
+
*/
|
|
360
|
+
export function formatToolName(
|
|
361
|
+
toolName: string,
|
|
362
|
+
serverName: string,
|
|
363
|
+
prefix: "server" | "none" | "short"
|
|
364
|
+
): string {
|
|
365
|
+
const p = getServerPrefix(serverName, prefix);
|
|
366
|
+
return p ? `${p}_${toolName}` : toolName;
|
|
367
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/app-bridge";
|
|
2
|
+
import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { ResourceFetchError, ResourceParseError } from "./errors.js";
|
|
4
|
+
import { logger } from "./logger.js";
|
|
5
|
+
import type { McpServerManager } from "./server-manager.js";
|
|
6
|
+
import type { UiResourceContent, UiResourceMeta } from "./types.js";
|
|
7
|
+
|
|
8
|
+
interface ResourceContentRecord {
|
|
9
|
+
uri?: string;
|
|
10
|
+
mimeType?: string;
|
|
11
|
+
text?: string;
|
|
12
|
+
blob?: string;
|
|
13
|
+
_meta?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class UiResourceHandler {
|
|
17
|
+
private log = logger.child({ component: "UiResourceHandler" });
|
|
18
|
+
|
|
19
|
+
constructor(private manager: McpServerManager) {}
|
|
20
|
+
|
|
21
|
+
async readUiResource(serverName: string, uri: string): Promise<UiResourceContent> {
|
|
22
|
+
const log = this.log.child({ server: serverName, uri });
|
|
23
|
+
|
|
24
|
+
if (!uri.startsWith("ui://")) {
|
|
25
|
+
throw new ResourceParseError(uri, "URI must start with ui://", { server: serverName });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
log.debug("Fetching UI resource");
|
|
29
|
+
|
|
30
|
+
let result: ReadResourceResult;
|
|
31
|
+
try {
|
|
32
|
+
result = await this.manager.readResource(serverName, uri);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
+
log.error("Failed to read resource", error instanceof Error ? error : undefined);
|
|
36
|
+
throw new ResourceFetchError(uri, message, {
|
|
37
|
+
server: serverName,
|
|
38
|
+
cause: error instanceof Error ? error : undefined,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const content = selectContent(result, uri);
|
|
43
|
+
const mimeType = content.mimeType;
|
|
44
|
+
|
|
45
|
+
if (mimeType && !isHtmlMimeType(mimeType)) {
|
|
46
|
+
log.warn("Unsupported MIME type", { mimeType });
|
|
47
|
+
throw new ResourceParseError(
|
|
48
|
+
uri,
|
|
49
|
+
`unsupported MIME type "${mimeType}" (expected text/html or ${RESOURCE_MIME_TYPE})`,
|
|
50
|
+
{ server: serverName, mimeType }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const html = toHtml(content);
|
|
55
|
+
if (!html.trim()) {
|
|
56
|
+
log.warn("Resource content is empty");
|
|
57
|
+
throw new ResourceParseError(uri, "content is empty", { server: serverName });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const contentMeta = extractUiMeta(content._meta);
|
|
61
|
+
const listMeta = extractUiMeta(this.getListResourceMeta(serverName, uri));
|
|
62
|
+
|
|
63
|
+
log.debug("Resource loaded successfully", {
|
|
64
|
+
contentLength: html.length,
|
|
65
|
+
hasCsp: !!contentMeta.csp || !!listMeta.csp,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
uri: content.uri ?? uri,
|
|
70
|
+
html,
|
|
71
|
+
mimeType: mimeType ?? RESOURCE_MIME_TYPE,
|
|
72
|
+
meta: {
|
|
73
|
+
csp: contentMeta.csp ?? listMeta.csp,
|
|
74
|
+
permissions: contentMeta.permissions ?? listMeta.permissions,
|
|
75
|
+
domain: contentMeta.domain ?? listMeta.domain,
|
|
76
|
+
prefersBorder: contentMeta.prefersBorder ?? listMeta.prefersBorder,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private getListResourceMeta(serverName: string, uri: string): Record<string, unknown> | undefined {
|
|
82
|
+
const connection = this.manager.getConnection(serverName);
|
|
83
|
+
if (!connection?.resources?.length) return undefined;
|
|
84
|
+
const resource = connection.resources.find((entry) => entry.uri === uri);
|
|
85
|
+
if (!resource || !resource._meta || typeof resource._meta !== "object") return undefined;
|
|
86
|
+
return resource._meta;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function selectContent(result: ReadResourceResult, preferredUri: string): ResourceContentRecord {
|
|
91
|
+
const contents = (result.contents ?? []) as ResourceContentRecord[];
|
|
92
|
+
if (contents.length === 0) {
|
|
93
|
+
throw new Error(`No contents returned for UI resource: ${preferredUri}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const byUri = contents.find((content) => content.uri === preferredUri);
|
|
97
|
+
if (byUri) return byUri;
|
|
98
|
+
|
|
99
|
+
const byHtmlMime = contents.find(
|
|
100
|
+
(content) => content.mimeType && isHtmlMimeType(content.mimeType)
|
|
101
|
+
);
|
|
102
|
+
if (byHtmlMime) return byHtmlMime;
|
|
103
|
+
|
|
104
|
+
return contents[0];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isHtmlMimeType(mimeType: string): boolean {
|
|
108
|
+
const normalized = mimeType.toLowerCase();
|
|
109
|
+
return normalized.startsWith("text/html") || normalized === RESOURCE_MIME_TYPE.toLowerCase();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function toHtml(content: ResourceContentRecord): string {
|
|
113
|
+
if (typeof content.text === "string") {
|
|
114
|
+
return content.text;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (typeof content.blob === "string") {
|
|
118
|
+
return Buffer.from(content.blob, "base64").toString("utf-8");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
throw new Error(`UI resource ${content.uri ?? "(unknown)"} did not include text or blob content`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function extractUiMeta(meta: Record<string, unknown> | undefined): UiResourceMeta {
|
|
125
|
+
if (!meta || typeof meta !== "object") return {};
|
|
126
|
+
const ui = meta.ui as Record<string, unknown> | undefined;
|
|
127
|
+
if (!ui || typeof ui !== "object") return {};
|
|
128
|
+
|
|
129
|
+
const out: UiResourceMeta = {};
|
|
130
|
+
|
|
131
|
+
if (ui.csp && typeof ui.csp === "object") {
|
|
132
|
+
out.csp = ui.csp as UiResourceMeta["csp"];
|
|
133
|
+
}
|
|
134
|
+
if (ui.permissions && typeof ui.permissions === "object") {
|
|
135
|
+
out.permissions = ui.permissions as UiResourceMeta["permissions"];
|
|
136
|
+
}
|
|
137
|
+
if (typeof ui.domain === "string") {
|
|
138
|
+
out.domain = ui.domain;
|
|
139
|
+
}
|
|
140
|
+
if (typeof ui.prefersBorder === "boolean") {
|
|
141
|
+
out.prefersBorder = ui.prefersBorder;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return out;
|
|
145
|
+
}
|