Skip to content

Commit 6441f22

Browse files
authored
reusable mcp client (#1646)
* feat: add listToolCallbacks method to McpClient interface and update related implementations * feat: add disableToolIdMangling option to McpServerConfig and update related implementations * feat: add devproxy support and upgrade sample script for Dev Proxy versioning * feat: integrate patchInputSchema function to standardize input schema processing * ✨: Enhance type imports across multiple files Added new type imports for better typing and code reliability.
1 parent c7a8184 commit 6441f22

18 files changed

Lines changed: 227 additions & 74 deletions

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"demux",
7474
"devcli",
7575
"devcontainers",
76+
"devproxy",
7677
"dfence",
7778
"docify",
7879
"Dockerized",

docs/src/content/docs/reference/scripts/mcp-clients.mdx

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ hero:
1111
details include a small play button or gear symbol, symbolizing operations
1212
or scripting functionality.
1313
file: ./mcp-clients.png
14-
1514
---
1615

1716
The [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) defines a protocol for sharing [tools](https://modelcontextprotocol.io/docs/concepts/tools)
@@ -40,10 +39,10 @@ This identifier is used to reference the server in the `mcpClient`.
4039

4140
```js
4241
const fs = await host.mcpServer({
43-
id: "filesystem",
44-
command: "npx",
45-
args: ["-y", "@modelcontextprotocol/server-filesystem", path.resolve(".")],
46-
})
42+
id: "filesystem",
43+
command: "npx",
44+
args: ["-y", "@modelcontextprotocol/server-filesystem", path.resolve(".")],
45+
});
4746
```
4847

4948
The server is automatically stopped when the prompt finishes.
@@ -52,28 +51,28 @@ The server is automatically stopped when the prompt finishes.
5251

5352
You can perform operations on tools. Queries are not cached and always communicate with the server.
5453

55-
- List tools:
54+
- List tools (metadata):
5655

57-
```js
58-
const tools = await fs.listTools()
59-
```
56+
```js
57+
const tools = await fs.listTools();
58+
```
6059

6160
- Call a tool:
6261

63-
```js
64-
const res = await fs.callTool("get_file_info", { path: "README.md" })
65-
```
62+
```js
63+
const res = await fs.callTool("get_file_info", { path: "README.md" });
64+
```
6665

6766
- Use the result:
6867

69-
```js
70-
const info = res.content[0].text
71-
```
68+
```js
69+
const info = res.content[0].text;
70+
```
7271

7372
The structure of the output depends on the tool, but it is designed to be consumed by an LLM. You will likely want to use `def` to store it in your prompt:
7473

7574
```js
76-
def("INFO", info)
75+
def("INFO", info);
7776
```
7877

7978
## Example: YouTube Transcript
@@ -83,12 +82,28 @@ of a YouTube video. It is listed in the [Docker MCP Catalog](https://hub.docker.
8382

8483
```js
8584
const yt = await host.mcpServer({
86-
id: "youtube_transcript",
87-
command: "docker",
88-
args: ["run", "-i", "--rm", "mcp/youtube-transcript"],
89-
})
90-
91-
const url = "https://youtu.be/ENunZe--7j0"
92-
const transcript = await yt.callTool("get_transcript", { url })
93-
console.log(`transcript: ${transcript.text}`)
85+
id: "youtube_transcript",
86+
command: "docker",
87+
args: ["run", "-i", "--rm", "mcp/youtube-transcript"],
88+
});
89+
90+
const url = "https://youtu.be/ENunZe--7j0";
91+
const transcript = await yt.callTool("get_transcript", { url });
92+
console.log(`transcript: ${transcript.text}`);
93+
```
94+
95+
## Sharing MCPs
96+
97+
By default, MCP servers are not shared between prompts. If you want to use the same MCP server in multiple prompts,
98+
you can use the `host.mcpClient` function to create a client that can be reused across prompts.
99+
100+
You can also get tools in a form that can be used in `defTool` and reuse it in multiple prompts:
101+
102+
```js
103+
const tools = await fs.listToolCallbacks();
104+
105+
await runPrompt((ctx) => {
106+
for (const tool of tools) ctx.defTool(tools);
107+
ctx....
108+
});
94109
```

packages/core/src/agent.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import { HTMLEscape } from "./htmlescape.js";
99
import { prettifyMarkdown } from "./markdown.js";
1010
import { TraceOptions } from "./trace.js";
1111
import { ellipse } from "./util.js";
12-
import type { ChatGenerationContext, WorkspaceFileCache } from "./types.js";
12+
import type {
13+
ChatGenerationContext,
14+
ChatTurnGenerationContext,
15+
WorkspaceFileCache,
16+
} from "./types.js";
1317

1418
import debug from "debug";
1519
const dbg = debug("agent:memory");
@@ -50,7 +54,6 @@ export async function agentQueryMemory(
5054
cache: AgentMemoryCache,
5155
ctx: ChatGenerationContext,
5256
query: string,
53-
_options: Required<TraceOptions>,
5457
) {
5558
if (!query) return undefined;
5659

packages/core/src/annotations.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import { deleteUndefinedValues } from "./cleaners.js";
1111
import { EMOJI_FAIL, EMOJI_WARNING } from "./constants.js";
1212
import { unfence } from "./unwrappers.js";
13-
import type { Diagnostic } from "./types.js";
13+
import type { Diagnostic, DiagnosticSeverity } from "./types.js";
1414

1515
// Regular expression for matching GitHub Actions annotations.
1616
// Example: ::error file=foo.js,line=10,endLine=11::Something went wrong.
@@ -39,7 +39,7 @@ const GITHUB_MARKDOWN_WARNINGS_RX =
3939
// Example: src/connection.ts(71,5): error TS1128: Declaration or statement expected.
4040
// src/connection.ts(71,5): error TS1128: Declaration or statement expected.
4141
const TYPESCRIPT_PARENTHESES_ANNOTATIONS_RX =
42-
/^(?<file>[^\(\n]+)\((?<line>\d+),(?<col>\d+)\):\s+(?<severity>error|warning)\s+(?<code>TS\d+):\s+(?<message>.+)$/gim;
42+
/^(?<file>[^(\n]+)\((?<line>\d+),(?<col>\d+)\):\s+(?<severity>error|warning)\s+(?<code>TS\d+):\s+(?<message>.+)$/gim;
4343
const ANNOTATIONS_RX = [
4444
TYPESCRIPT_PARENTHESES_ANNOTATIONS_RX,
4545
TYPESCRIPT_ANNOTATIONS_RX,
@@ -187,6 +187,7 @@ export function convertGithubMarkdownAnnotationsToItems(text: string) {
187187
* @returns A formatted string representing the Diagnostic as a list item.
188188
*/
189189
export function convertAnnotationToItem(d: Diagnostic) {
190+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
190191
const { severity, message, filename, code, range } = d;
191192
const line = range?.[0]?.[0];
192193
return `- ${SEV_EMOJI_MAP[severity?.toLowerCase()] ?? "info"} ${message}${filename ? ` (\`${filename}${line ? `#L${line}` : ""}\`)` : ""}`;

packages/core/src/anthropic.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { LanguageModelConfiguration } from "./server/messages.js";
4040
import { deleteUndefinedValues } from "./cleaners.js";
4141
import debug from "debug";
4242
import { providerFeatures } from "./features.js";
43+
import { LanguageModelInfo } from "./types.js";
4344
const dbg = debug("genaiscript:anthropic");
4445
const dbgMessages = debug("genaiscript:anthropic:msg");
4546

packages/core/src/ast.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
/// <reference types="./types/prompt_template.d.ts" />
45
// Import necessary regular expressions for file type detection and host utilities
56
import { GENAI_ANYJS_REGEX, GENAI_ANYTS_REGEX } from "./constants.js";
67
import { Project } from "./server/messages.js";

packages/core/src/azureaisearch.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import { LanguageModelConfiguration } from "./server/messages.js";
1414
import { chunk } from "./encoders.js";
1515
import { genaiscriptDebug } from "./debug.js";
1616
import { SearchClient, SearchIndexClient, AzureKeyCredential } from "@azure/search-documents";
17+
import type {
18+
ElementOrArray,
19+
TextChunk,
20+
VectorIndexOptions,
21+
VectorSearchOptions,
22+
WorkspaceFile,
23+
WorkspaceFileIndex,
24+
WorkspaceFileWithScore,
25+
} from "./types.js";
1726

1827
const dbg = genaiscriptDebug("azureaisearch");
1928

packages/core/src/azuredevops.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export async function azureDevOpsUpdatePullRequestDescription(
110110
// query pull request
111111
const pr = await findPullRequest(info);
112112
if (!pr) return;
113+
// eslint-disable-next-line prefer-const
113114
let { pullRequestId, description } = pr;
114115

115116
text = prettifyMarkdown(text);

packages/core/src/azureopenai.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const azureManagementListModels: ListModelsFunction = async (cfg, options) => {
3434
const { base } = cfg;
3535
const subscriptionId = process.env.AZURE_OPENAI_SUBSCRIPTION_ID;
3636
let resourceGroupName = process.env.AZURE_OPENAI_RESOURCE_GROUP;
37-
const accountName = /^https:\/\/([^\.]+)\./.exec(base)[1];
37+
const accountName = /^https:\/\/([^.]+)\./.exec(base)[1];
3838

3939
if (!subscriptionId || !accountName) {
4040
dbg("subscriptionId or accountName is missing, returning an empty model list");
@@ -78,7 +78,7 @@ const azureManagementListModels: ListModelsFunction = async (cfg, options) => {
7878
`https://management.azure.com/subscriptions/${subscriptionId}/resources?api-version=2021-04-01`,
7979
);
8080
const resource = resources.value.find((r) => r.name === accountName);
81-
resourceGroupName = /\/resourceGroups\/([^\/]+)\/providers\//.exec(resource?.id)[1];
81+
resourceGroupName = /\/resourceGroups\/([^/]+)\/providers\//.exec(resource?.id)[1];
8282
if (!resourceGroupName) {
8383
dbg("unable to extract resource group name from resource id");
8484
throw new Error("Resource group not found");

packages/core/src/azuretoken.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
ManagedIdentityCredential,
2727
WorkloadIdentityCredential,
2828
} from "@azure/identity";
29+
import type { SerializedError } from "./types.js";
2930

3031
/**
3132
* This module provides functions to handle Azure authentication tokens,

0 commit comments

Comments
 (0)