flamecast 0.0.1

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.
@@ -0,0 +1,4 @@
1
+
2
+ > @smithery/act-sdk@0.1.0 build /Users/anirudh/conductor/workspaces/act-sdk/rabat/packages/act-sdk
3
+ > tsc
4
+
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # runner-example
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.1.10. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "flamecast",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "check-types": "tsc --noEmit",
19
+ "check": "biome check",
20
+ "fmt": "biome check --write --unsafe"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "latest",
24
+ "tsx": "^4.21.0",
25
+ "@smithery/typescript-config": "workspace:*"
26
+ },
27
+ "peerDependencies": {
28
+ "typescript": "^5.0.0"
29
+ },
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^1.25.2",
32
+ "@smithery/runner": "workspace:*",
33
+ "ai": "^6.0.3",
34
+ "dotenv": "^17.2.3",
35
+ "zod": "^4.1.13"
36
+ }
37
+ }
@@ -0,0 +1,12 @@
1
+ export type {
2
+ CacheAdapter,
3
+ CachedResolution,
4
+ GenerateCacheKeyFn,
5
+ } from "./types.js"
6
+ export { MemoryCache } from "./memory-cache.js"
7
+ export { LocalFileStorage } from "./local-file-storage.js"
8
+ export {
9
+ generateCacheKey,
10
+ generateToolSchemaHash,
11
+ normalizeServerIdentifier,
12
+ } from "./utils.js"
@@ -0,0 +1,72 @@
1
+ import { existsSync, mkdirSync } from "node:fs"
2
+ import { readdir, readFile, unlink, writeFile } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import type { CacheAdapter, CachedResolution } from "./types.js"
5
+
6
+ /**
7
+ * File-based cache adapter storing resolutions in a local directory.
8
+ * Each cached resolution is stored as a JSON file named by its key.
9
+ *
10
+ * Default directory: .smithery/ in the current working directory.
11
+ */
12
+ export class LocalFileStorage implements CacheAdapter {
13
+ private readonly cacheDir: string
14
+
15
+ constructor(cacheDir?: string) {
16
+ this.cacheDir = cacheDir ?? path.join(process.cwd(), ".smithery")
17
+ }
18
+
19
+ private ensureDir(): void {
20
+ if (!existsSync(this.cacheDir)) {
21
+ mkdirSync(this.cacheDir, { recursive: true })
22
+ }
23
+ }
24
+
25
+ private getFilePath(key: string): string {
26
+ return path.join(this.cacheDir, `${key}.json`)
27
+ }
28
+
29
+ async get(key: string): Promise<CachedResolution | null> {
30
+ const filePath = this.getFilePath(key)
31
+
32
+ if (!existsSync(filePath)) {
33
+ return null
34
+ }
35
+
36
+ try {
37
+ const content = await readFile(filePath, "utf8")
38
+ return JSON.parse(content) as CachedResolution
39
+ } catch {
40
+ // File exists but is invalid - treat as cache miss
41
+ return null
42
+ }
43
+ }
44
+
45
+ async set(key: string, value: CachedResolution): Promise<void> {
46
+ this.ensureDir()
47
+ const filePath = this.getFilePath(key)
48
+ await writeFile(filePath, JSON.stringify(value, null, 2))
49
+ }
50
+
51
+ async delete(key: string): Promise<void> {
52
+ const filePath = this.getFilePath(key)
53
+
54
+ if (existsSync(filePath)) {
55
+ await unlink(filePath)
56
+ }
57
+ }
58
+
59
+ async clear(): Promise<void> {
60
+ if (!existsSync(this.cacheDir)) {
61
+ return
62
+ }
63
+
64
+ const files = await readdir(this.cacheDir, { withFileTypes: true })
65
+
66
+ await Promise.all(
67
+ files
68
+ .filter(file => file.isFile() && file.name.endsWith(".json"))
69
+ .map(file => unlink(path.join(this.cacheDir, file.name))),
70
+ )
71
+ }
72
+ }
@@ -0,0 +1,33 @@
1
+ import type { CacheAdapter, CachedResolution } from "./types.js"
2
+
3
+ /**
4
+ * In-memory cache adapter using a Map.
5
+ * Useful for development, testing, or short-lived processes.
6
+ * Data is lost when the process terminates.
7
+ */
8
+ export class MemoryCache implements CacheAdapter {
9
+ private cache: Map<string, CachedResolution> = new Map()
10
+
11
+ async get(key: string): Promise<CachedResolution | null> {
12
+ return this.cache.get(key) ?? null
13
+ }
14
+
15
+ async set(key: string, value: CachedResolution): Promise<void> {
16
+ this.cache.set(key, value)
17
+ }
18
+
19
+ async delete(key: string): Promise<void> {
20
+ this.cache.delete(key)
21
+ }
22
+
23
+ async clear(): Promise<void> {
24
+ this.cache.clear()
25
+ }
26
+
27
+ /**
28
+ * Get the number of cached entries (for debugging)
29
+ */
30
+ get size(): number {
31
+ return this.cache.size
32
+ }
33
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * A cached resolution of an intent to a tool call
3
+ */
4
+ export interface CachedResolution {
5
+ /** Cache key (hash of intent + servers) */
6
+ key: string
7
+
8
+ /** Resolved server ID */
9
+ serverId: string
10
+
11
+ /** Resolved server qualified name */
12
+ serverQualifiedName: string
13
+
14
+ /** Resolved tool name */
15
+ toolName: string
16
+
17
+ /** Extracted parameters */
18
+ params: Record<string, unknown>
19
+
20
+ /** ISO timestamp of when this was resolved */
21
+ resolvedAt: string
22
+
23
+ /** Hash of the tool's input schema (for cache invalidation) */
24
+ toolSchemaHash: string
25
+ }
26
+
27
+ /**
28
+ * Interface for pluggable cache implementations
29
+ */
30
+ export interface CacheAdapter {
31
+ /**
32
+ * Get a cached resolution by key
33
+ */
34
+ get(key: string): Promise<CachedResolution | null>
35
+
36
+ /**
37
+ * Store a resolution in the cache
38
+ */
39
+ set(key: string, value: CachedResolution): Promise<void>
40
+
41
+ /**
42
+ * Delete a specific cache entry
43
+ */
44
+ delete(key: string): Promise<void>
45
+
46
+ /**
47
+ * Clear all cached entries
48
+ */
49
+ clear(): Promise<void>
50
+ }
51
+
52
+ /**
53
+ * Function type for custom cache key generation
54
+ */
55
+ export type GenerateCacheKeyFn = (prompt: string, servers: string[]) => string
@@ -0,0 +1,44 @@
1
+ import crypto from "node:crypto"
2
+
3
+ /**
4
+ * Generates a deterministic cache key from a prompt and list of servers.
5
+ * Uses SHA-256 hash for a stable, filesystem-safe key.
6
+ */
7
+ export function generateCacheKey(prompt: string, servers: string[]): string {
8
+ const normalizedPrompt = prompt.trim().toLowerCase()
9
+ const sortedServers = [...servers].sort()
10
+ const input = JSON.stringify({
11
+ prompt: normalizedPrompt,
12
+ servers: sortedServers,
13
+ })
14
+ return crypto.createHash("sha256").update(input).digest("hex")
15
+ }
16
+
17
+ /**
18
+ * Generates a hash of tool schemas for cache invalidation.
19
+ * If schemas change, the hash changes and cache is invalidated.
20
+ */
21
+ export function generateToolSchemaHash(
22
+ tools: Array<{ name: string; inputSchema: unknown }>,
23
+ ): string {
24
+ const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name))
25
+ const schemaData = sortedTools.map(t => ({
26
+ name: t.name,
27
+ schema: t.inputSchema,
28
+ }))
29
+ return crypto
30
+ .createHash("sha256")
31
+ .update(JSON.stringify(schemaData))
32
+ .digest("hex")
33
+ }
34
+
35
+ /**
36
+ * Normalizes a server identifier for consistent cache key generation.
37
+ * Removes protocol and trailing slashes.
38
+ */
39
+ export function normalizeServerIdentifier(server: string): string {
40
+ return server
41
+ .replace(/^https?:\/\//, "")
42
+ .replace(/\/+$/, "")
43
+ .toLowerCase()
44
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,10 @@
1
+ export class MCPAuthenticationRequiredError extends Error {
2
+ constructor(
3
+ message: string,
4
+ public readonly authorizationUrl: string,
5
+ ) {
6
+ super(`\n\n⚠️ ⚠️ ⚠️ ⚠️ ⚠️\n${message}\n\n${authorizationUrl}\n⚠️ ⚠️ ⚠️ ⚠️ ⚠️\n\n`)
7
+ this.name = "MCPAuthenticationRequiredError"
8
+ this.authorizationUrl = authorizationUrl
9
+ }
10
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./cache/index.js"
2
+ export * from "./errors.js"
3
+ export * from "./types.js"
4
+ export { Runner } from "./runner.js"
package/src/runner.ts ADDED
@@ -0,0 +1,440 @@
1
+ import {
2
+ addServerToProfile,
3
+ callTool,
4
+ exchangeApiKeyForToken,
5
+ listTools,
6
+ } from "@smithery/runner"
7
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js"
8
+ import { generateText, jsonSchema, Output, stepCountIs, tool } from "ai"
9
+ import type { z } from "zod"
10
+
11
+ import type {
12
+ CacheAdapter,
13
+ CachedResolution,
14
+ GenerateCacheKeyFn,
15
+ } from "./cache/types.js"
16
+ import {
17
+ generateCacheKey as defaultGenerateCacheKey,
18
+ generateToolSchemaHash,
19
+ normalizeServerIdentifier,
20
+ } from "./cache/utils.js"
21
+ import { MCPAuthenticationRequiredError } from "./errors.js"
22
+ import {
23
+ type ActOptions,
24
+ type ConnectionConfig,
25
+ defaultSchema,
26
+ emptyInputSchema,
27
+ type OnAuthenticationRequiredCallback,
28
+ type PlanOptions,
29
+ type PollResult,
30
+ type RunnerInitOptions,
31
+ type RunOptions,
32
+ type ServerInput,
33
+ type ToolCallTemplate,
34
+ } from "./types.js"
35
+ import { fillTemplate, getSchemaKeys } from "./utils.js"
36
+
37
+ export class Runner {
38
+ private readonly serviceToken: string
39
+ private readonly profileSlug: string
40
+ private serverConfigMap: Map<string, string> = new Map()
41
+ private onAuthenticationRequired?: OnAuthenticationRequiredCallback
42
+ private cache?: CacheAdapter
43
+ private generateCacheKey: GenerateCacheKeyFn
44
+
45
+ // Returns a set of server URLs that have been connected to the runner
46
+ public get servers(): Set<string> {
47
+ return new Set(this.serverConfigMap.keys())
48
+ }
49
+
50
+ /**
51
+ * NOTE: This constructor is private to prevent direct instantiation.
52
+ * Use the static init() method to create a new Runner instance.
53
+ */
54
+ private constructor({
55
+ profileSlug,
56
+ serviceToken,
57
+ onAuthenticationRequired,
58
+ cache,
59
+ generateCacheKey,
60
+ }: {
61
+ profileSlug: string
62
+ serviceToken: string
63
+ onAuthenticationRequired?: OnAuthenticationRequiredCallback
64
+ cache?: CacheAdapter
65
+ generateCacheKey?: GenerateCacheKeyFn
66
+ }) {
67
+ this.profileSlug = profileSlug
68
+ this.serviceToken = serviceToken
69
+ this.onAuthenticationRequired = onAuthenticationRequired
70
+ this.cache = cache
71
+ this.generateCacheKey = generateCacheKey ?? defaultGenerateCacheKey
72
+ }
73
+
74
+ public static async init({
75
+ apiKey = process.env.SMITHERY_API_KEY,
76
+ onAuthenticationRequired,
77
+ cache,
78
+ generateCacheKey,
79
+ }: RunnerInitOptions = {}): Promise<Runner> {
80
+ if (!apiKey) {
81
+ throw new Error(
82
+ "API key is required. Get one from: https://smithery.ai/account/api-keys",
83
+ )
84
+ }
85
+
86
+ const { serviceToken, profileSlug } = await exchangeApiKeyForToken(apiKey)
87
+
88
+ return new Runner({
89
+ profileSlug,
90
+ serviceToken,
91
+ onAuthenticationRequired,
92
+ cache,
93
+ generateCacheKey,
94
+ })
95
+ }
96
+
97
+ private getServerUrl(server: string): string {
98
+ return server.startsWith("http://") || server.startsWith("https://")
99
+ ? server
100
+ : `https://server.smithery.ai/${server}/mcp`
101
+ }
102
+
103
+ public async use(server: string): Promise<ConnectionConfig> {
104
+ // Delegate to resolveServerInput which handles auth callbacks and caching
105
+ return this.resolveServerInput(server)
106
+ }
107
+
108
+ /**
109
+ * Polls the connection status for a server.
110
+ * Returns status without throwing errors.
111
+ *
112
+ * @param server - Server name or URL
113
+ * @returns "success" if connected, "needs_auth" if auth required, "error" if failed
114
+ */
115
+ public async poll(server: string): Promise<PollResult> {
116
+ // Check if already connected
117
+ if (this.serverConfigMap.has(server)) {
118
+ return "success"
119
+ }
120
+
121
+ const serverUrl = this.getServerUrl(server)
122
+
123
+ // Attempt connection
124
+ const addResult = await addServerToProfile({
125
+ profileSlug: this.profileSlug,
126
+ input: { mcpUrl: serverUrl },
127
+ token: this.serviceToken,
128
+ })
129
+
130
+ if (addResult.status.state === "connected") {
131
+ // Cache the successful connection
132
+ this.serverConfigMap.set(server, addResult.configId)
133
+ return "success"
134
+ }
135
+
136
+ if (addResult.status.state === "auth_required") {
137
+ return "needs_auth"
138
+ }
139
+
140
+ return "error"
141
+ }
142
+
143
+ /**
144
+ * Resolves a ServerInput to a ConnectionConfig, connecting on-the-fly if needed.
145
+ */
146
+ private async resolveServerInput(
147
+ input: ServerInput,
148
+ ): Promise<ConnectionConfig> {
149
+ // Already a ConnectionConfig - return as-is
150
+ if (typeof input !== "string") {
151
+ return input
152
+ }
153
+
154
+ // String server name - check if already connected
155
+ const server = input
156
+ const existingConfigId = this.serverConfigMap.get(server)
157
+ if (existingConfigId) {
158
+ return {
159
+ serverUrl: this.getServerUrl(server),
160
+ configId: existingConfigId,
161
+ }
162
+ }
163
+
164
+ // Connect on-the-fly (same logic as use())
165
+ const serverUrl = this.getServerUrl(server)
166
+ let addResult = await addServerToProfile({
167
+ profileSlug: this.profileSlug,
168
+ input: { mcpUrl: serverUrl },
169
+ token: this.serviceToken,
170
+ })
171
+
172
+ // Handle auth required
173
+ if (addResult.status.state === "auth_required") {
174
+ if (this.onAuthenticationRequired) {
175
+ // Call the callback (expected to block until auth completes)
176
+ await this.onAuthenticationRequired(
177
+ addResult.status.authorizationUrl,
178
+ serverUrl,
179
+ )
180
+ // Retry connection after callback returns
181
+ addResult = await addServerToProfile({
182
+ profileSlug: this.profileSlug,
183
+ input: { mcpUrl: serverUrl },
184
+ token: this.serviceToken,
185
+ })
186
+ } else {
187
+ // No callback - throw immediately
188
+ throw new MCPAuthenticationRequiredError(
189
+ `Authentication required. To authenticate, visit the following URL and follow the instructions:`,
190
+ addResult.status.authorizationUrl,
191
+ )
192
+ }
193
+ }
194
+
195
+ if (addResult.status.state !== "connected") {
196
+ throw new Error(
197
+ `Failed to connect to MCP server: ${JSON.stringify(addResult.status)}`,
198
+ )
199
+ }
200
+
201
+ // Cache the connection
202
+ this.serverConfigMap.set(server, addResult.configId)
203
+ return { serverUrl, configId: addResult.configId }
204
+ }
205
+
206
+ public async listTools(): Promise<Tool[]> {
207
+ const tools = await Promise.all(
208
+ Array.from(this.serverConfigMap.entries()).map(async ([_, configId]) => {
209
+ return await listTools(this.profileSlug, configId, this.serviceToken)
210
+ }),
211
+ )
212
+ return tools.flat()
213
+ }
214
+
215
+ /**
216
+ * Plans a tool call without executing it. Returns a serializable template
217
+ * where input values are parameterized with {{key}} placeholders.
218
+ */
219
+ public async plan<TInput extends z.ZodTypeAny = typeof emptyInputSchema>(
220
+ prompt: string,
221
+ using: ServerInput[],
222
+ options: PlanOptions<TInput> = {},
223
+ ): Promise<ToolCallTemplate> {
224
+ const { model = "anthropic/claude-haiku-4.5" } = options
225
+ const inputSchema = (options.inputSchema ?? emptyInputSchema) as TInput
226
+
227
+ // Resolve all server inputs to ConnectionConfigs (connects on-the-fly if needed)
228
+ const resolvedConfigs = await Promise.all(
229
+ using.map(input => this.resolveServerInput(input)),
230
+ )
231
+
232
+ // Generate normalized server identifiers for cache key
233
+ const serverIds = using.map(s =>
234
+ typeof s === "string"
235
+ ? normalizeServerIdentifier(s)
236
+ : normalizeServerIdentifier(s.serverUrl),
237
+ )
238
+
239
+ // Generate cache key
240
+ const cacheKey = this.generateCacheKey(prompt, serverIds)
241
+
242
+ // Get input keys from schema
243
+ const inputKeys = getSchemaKeys(inputSchema)
244
+
245
+ // Get tools from resolved servers, tracking which server provides each tool
246
+ const mcpToolsWithConfigs = await Promise.all(
247
+ resolvedConfigs.map(async (config: ConnectionConfig) => {
248
+ const tools = await listTools(
249
+ this.profileSlug,
250
+ config.configId,
251
+ this.serviceToken,
252
+ )
253
+ return tools.map((t: Tool) => ({
254
+ tool: t,
255
+ configId: config.configId,
256
+ serverUrl: config.serverUrl,
257
+ }))
258
+ }),
259
+ ).then(tools => tools.flat())
260
+
261
+ // Calculate tool schema hash for cache validation
262
+ const toolSchemaHash = generateToolSchemaHash(
263
+ mcpToolsWithConfigs.map(({ tool: mcpTool }: { tool: Tool }) => ({
264
+ name: mcpTool.name,
265
+ inputSchema: mcpTool.inputSchema,
266
+ })),
267
+ )
268
+
269
+ // Check cache before LLM call
270
+ if (this.cache) {
271
+ try {
272
+ const cached = await this.cache.get(cacheKey)
273
+ if (cached && cached.toolSchemaHash === toolSchemaHash) {
274
+ // Cache hit with valid schema - return cached template
275
+ return {
276
+ toolName: cached.toolName,
277
+ argsTemplate: cached.params,
278
+ server: cached.serverId,
279
+ }
280
+ }
281
+ } catch {
282
+ // Cache error - fall through to LLM call
283
+ }
284
+ }
285
+
286
+ // Build tool -> server lookup
287
+ const toolServerMap = new Map<string, string>()
288
+ for (const { tool: mcpTool, serverUrl } of mcpToolsWithConfigs) {
289
+ toolServerMap.set(mcpTool.name, serverUrl)
290
+ }
291
+
292
+ // Create tools WITHOUT execute functions (plan mode - no execution)
293
+ const aiTools = Object.fromEntries(
294
+ mcpToolsWithConfigs.map(({ tool: mcpTool }: { tool: Tool }) => [
295
+ mcpTool.name,
296
+ tool({
297
+ description: mcpTool.description ?? "",
298
+ inputSchema: jsonSchema(mcpTool.inputSchema),
299
+ }),
300
+ ]),
301
+ )
302
+
303
+ // Build system prompt instructing AI to use placeholders
304
+ const systemPrompt =
305
+ inputKeys.length > 0
306
+ ? `You have access to the following input variables that will be provided at runtime. Use them as {{variableName}} placeholders in your tool arguments:
307
+ ${inputKeys.map((key: string) => `- {{${key}}}`).join("\n")}
308
+
309
+ IMPORTANT: When a tool argument should use one of these variables, use the {{variableName}} syntax exactly. Do not use literal values for these variables.`
310
+ : undefined
311
+
312
+ // Call generateText with stopWhen to get exactly one tool call
313
+ const result = await generateText({
314
+ model,
315
+ tools: aiTools,
316
+ prompt,
317
+ toolChoice: "required",
318
+ stopWhen: stepCountIs(1),
319
+ ...(systemPrompt ? { system: systemPrompt } : {}),
320
+ })
321
+
322
+ // Extract the tool call
323
+ const toolCall = result.toolCalls[0]
324
+ if (!toolCall) {
325
+ throw new Error("No tool call was generated by the model")
326
+ }
327
+
328
+ const serverUrl = toolServerMap.get(toolCall.toolName)
329
+ if (!serverUrl) {
330
+ throw new Error(`No server found for tool: ${toolCall.toolName}`)
331
+ }
332
+
333
+ // Store result in cache
334
+ if (this.cache) {
335
+ try {
336
+ const resolution: CachedResolution = {
337
+ key: cacheKey,
338
+ serverId: serverUrl,
339
+ serverQualifiedName: serverUrl,
340
+ toolName: toolCall.toolName,
341
+ params: toolCall.input as Record<string, unknown>,
342
+ resolvedAt: new Date().toISOString(),
343
+ toolSchemaHash,
344
+ }
345
+ await this.cache.set(cacheKey, resolution)
346
+ } catch {
347
+ // Cache error - continue without caching
348
+ }
349
+ }
350
+
351
+ // Return serializable template with server info
352
+ return {
353
+ toolName: toolCall.toolName,
354
+ argsTemplate: toolCall.input as Record<string, unknown>,
355
+ server: serverUrl,
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Executes a tool call template with the provided input values.
361
+ * Completely decoupled from plan() - can be called from a different endpoint.
362
+ * Uses the server URL from the template to resolve the connection.
363
+ */
364
+ public async run<TOutput extends z.ZodTypeAny = typeof defaultSchema>(
365
+ template: ToolCallTemplate,
366
+ input: Record<string, unknown>,
367
+ options: RunOptions<TOutput> = {},
368
+ ): Promise<z.infer<TOutput>> {
369
+ const { model = "anthropic/claude-haiku-4.5" } = options
370
+ const outputSchema = (options.outputSchema ?? defaultSchema) as TOutput
371
+ const hasCustomOutputSchema = options.outputSchema !== undefined
372
+
373
+ // Resolve server - could be serverUrl or server name, connects on-the-fly if needed
374
+ let configId = this.serverConfigMap.get(template.server)
375
+ if (!configId) {
376
+ // Try to connect on-the-fly
377
+ const resolved = await this.resolveServerInput(template.server)
378
+ configId = resolved.configId
379
+ }
380
+
381
+ // Fill template placeholders with input values
382
+ const filledArgs = fillTemplate(template.argsTemplate, input)
383
+
384
+ console.log(`Calling MCP tool: ${template.toolName} with args:`, filledArgs)
385
+
386
+ // Execute the tool
387
+ const result = await callTool(
388
+ this.profileSlug,
389
+ configId,
390
+ template.toolName,
391
+ filledArgs,
392
+ this.serviceToken,
393
+ )
394
+
395
+ console.log("result", result)
396
+
397
+ if (result.isError) {
398
+ throw new Error(`Tool execution error: ${JSON.stringify(result.content)}`)
399
+ }
400
+
401
+ // If outputSchema is provided, use generateText to conform result
402
+ if (hasCustomOutputSchema) {
403
+ const { output } = await generateText({
404
+ model,
405
+ prompt: `Based on the following tool result, extract and structure the information according to the schema.\n\nTool: ${template.toolName}\nResult: ${JSON.stringify(result.content)}`,
406
+ output: Output.object({ schema: outputSchema }),
407
+ })
408
+ return output as z.infer<TOutput>
409
+ }
410
+
411
+ // Return raw result wrapped in default schema format
412
+ return { result: JSON.stringify(result.content) } as z.infer<TOutput>
413
+ }
414
+
415
+ /**
416
+ * Acts on a prompt by determining which tool to call and executing it.
417
+ * Convenience method that combines plan() + run().
418
+ */
419
+ public async act<
420
+ TInput extends z.ZodTypeAny = typeof emptyInputSchema,
421
+ TOutput extends z.ZodTypeAny = typeof defaultSchema,
422
+ >(
423
+ prompt: string,
424
+ using: ServerInput[],
425
+ options: ActOptions<TInput, TOutput> = {},
426
+ ): Promise<z.infer<TOutput>> {
427
+ const template = await this.plan(prompt, using, {
428
+ model: options.model,
429
+ inputSchema: options.inputSchema,
430
+ })
431
+ return this.run(
432
+ template,
433
+ {},
434
+ {
435
+ model: options.model,
436
+ outputSchema: options.outputSchema,
437
+ },
438
+ )
439
+ }
440
+ }
package/src/types.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { LanguageModel } from "ai"
2
+ import { z } from "zod"
3
+ import type { CacheAdapter, GenerateCacheKeyFn } from "./cache/types.js"
4
+
5
+ export type ConnectionConfig = {
6
+ serverUrl: string
7
+ configId: string
8
+ }
9
+
10
+ // Union type for act/plan's `using` parameter - accepts either pre-connected config or server name string
11
+ export type ServerInput = ConnectionConfig | string
12
+
13
+ // Poll result type
14
+ export type PollResult = "success" | "needs_auth" | "error"
15
+
16
+ // Auth callback type - called when authentication is required during connection
17
+ export type OnAuthenticationRequiredCallback = (
18
+ authorizationUrl: string,
19
+ serverUrl: string,
20
+ ) => Promise<void>
21
+
22
+ // Init options type
23
+ export type RunnerInitOptions = {
24
+ apiKey?: string
25
+ onAuthenticationRequired?: OnAuthenticationRequiredCallback
26
+ /** Optional cache adapter for storing plan() resolutions */
27
+ cache?: CacheAdapter
28
+ /** Optional custom cache key generation function */
29
+ generateCacheKey?: GenerateCacheKeyFn
30
+ }
31
+
32
+ export const defaultSchema = z.object({ result: z.string() })
33
+ export const emptyInputSchema = z.object({})
34
+
35
+ // Serializable template returned by plan()
36
+ export type ToolCallTemplate = {
37
+ toolName: string
38
+ argsTemplate: Record<string, unknown>
39
+ server: string // server URL to execute the tool on
40
+ }
41
+
42
+ // Options for plan()
43
+ export type PlanOptions<TInput extends z.ZodTypeAny = typeof emptyInputSchema> =
44
+ {
45
+ model?: LanguageModel
46
+ inputSchema?: TInput
47
+ }
48
+
49
+ // Options for run()
50
+ export type RunOptions<TOutput extends z.ZodTypeAny = typeof defaultSchema> = {
51
+ model?: LanguageModel
52
+ outputSchema?: TOutput
53
+ }
54
+
55
+ // Options for act() - combines plan + run options
56
+ export type ActOptions<
57
+ TInput extends z.ZodTypeAny = typeof emptyInputSchema,
58
+ TOutput extends z.ZodTypeAny = typeof defaultSchema,
59
+ > = PlanOptions<TInput> & RunOptions<TOutput>
package/src/utils.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { z } from "zod"
2
+
3
+ /**
4
+ * Fills {{key}} placeholders in template with input values.
5
+ */
6
+ export function fillTemplate(
7
+ template: Record<string, unknown>,
8
+ input: Record<string, unknown>,
9
+ ): Record<string, unknown> {
10
+ function fillValue(value: unknown): unknown {
11
+ if (typeof value === "string") {
12
+ return value.replace(/\{\{(\w+)\}\}/g, (match, key) => {
13
+ if (key in input) {
14
+ return String(input[key])
15
+ }
16
+ return match
17
+ })
18
+ }
19
+ if (Array.isArray(value)) {
20
+ return value.map(fillValue)
21
+ }
22
+ if (value !== null && typeof value === "object") {
23
+ return fillTemplate(value as Record<string, unknown>, input)
24
+ }
25
+ return value
26
+ }
27
+
28
+ const result: Record<string, unknown> = {}
29
+ for (const [key, value] of Object.entries(template)) {
30
+ result[key] = fillValue(value)
31
+ }
32
+ return result
33
+ }
34
+
35
+ /**
36
+ * Extracts the keys from a Zod object schema.
37
+ */
38
+ export function getSchemaKeys(schema: z.ZodTypeAny): string[] {
39
+ if (schema instanceof z.ZodObject) {
40
+ return Object.keys(schema.shape)
41
+ }
42
+ return []
43
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@smithery/typescript-config/tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }