Skip to content

Commit 8acdcf7

Browse files
Copilotpelikhan
andauthored
Add system.resources system script for URL content resolution and update documentation (#1886)
* Initial plan * Add system.mcp_read_resource system script with basic functionality and documentation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Add integration tests and finalize system.mcp_read_resource implementation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Refactor MCP resource reading tools: rename `mcp_read_resource` to `resource_read`, update documentation, and remove obsolete test scripts. * Update URL description in resource_read tool and remove required flag * Refactor resource handling: rename `resource_read` to `resources`, add `resource_list` tool, and update documentation. Remove obsolete scripts. * Update resource documentation to include system.resources tools Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Co-authored-by: Peli de Halleux <pelikhan@users.noreply.github.com>
1 parent 9c1b6b8 commit 8acdcf7

9 files changed

Lines changed: 418 additions & 1 deletion

File tree

docs/src/components/BuiltinTools.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import { LinkCard } from '@astrojs/starlight/components';
4141
<LinkCard title="python_code_interpreter_run" description="Executes python 3.12 code for Data Analysis tasks in a docker container. The process output is returned. Do not generate visualizations. The only packages available are numpy===2.1.3, pandas===2.2.3, scipy===1.14.1, matplotlib===3.9.2. There is NO network connectivity. Do not attempt to install other packages or make web requests. You must copy all the necessary files or pass all the data because the python code runs in a separate container." href="/genaiscript/reference/scripts/system#systempython_code_interpreter" />
4242
<LinkCard title="python_code_interpreter_copy_files_to_container" description="Copy files from the workspace file system to the container file system. NO absolute paths. Returns the path of each file copied in the python container." href="/genaiscript/reference/scripts/system#systempython_code_interpreter" />
4343
<LinkCard title="python_code_interpreter_read_file" description="Reads a file from the container file system. No absolute paths." href="/genaiscript/reference/scripts/system#systempython_code_interpreter" />
44+
<LinkCard title="resource_list" description="List available resources from the host. Returns a list of available resource URIs and their descriptions." href="/genaiscript/reference/scripts/system#systemresources" />
45+
<LinkCard title="resource_read" description="Read the content of a resource from a URL. Resolves various protocols and returns the content of the files found at the URL." href="/genaiscript/reference/scripts/system#systemresources" />
4446
<LinkCard title="retrieval_fuzz_search" description="Search for keywords using the full text of files and a fuzzy distance." href="/genaiscript/reference/scripts/system#systemretrieval_fuzz_search" />
4547
<LinkCard title="retrieval_vector_search" description="Search files using embeddings and similarity distance." href="/genaiscript/reference/scripts/system#systemretrieval_vector_search" />
4648
<LinkCard title="retrieval_web_search" description="Search the web for a user query using Tavily or Bing Search." href="/genaiscript/reference/scripts/system#systemretrieval_web_search" />

docs/src/content/docs/blog/mcp-resources.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,36 @@ await host.resolveResource("https://github.com/user/repo.git/path/to/file");
151151
await host.resolveResource("vscode://vsls-contrib.gistfs/open?gist=123&file=script.js");
152152
```
153153

154+
## System Resource Tools
155+
156+
GenAIScript provides built-in tools for working with resources through the `system.resources` system script. These tools make it easy to list and read resources in your scripts:
157+
158+
### `resource_list`
159+
160+
Lists all available resources from the host, returning their URIs and descriptions.
161+
162+
```js
163+
script({
164+
system: ["system.resources"]
165+
})
166+
167+
$`Use the resource_list tool to see what resources are available, then read one of them.`
168+
```
169+
170+
### `resource_read`
171+
172+
Reads content from a URL using the same resolution logic as `host.resolveResource`. Supports all the URL patterns mentioned above.
173+
174+
```js
175+
script({
176+
system: ["system.resources"]
177+
})
178+
179+
$`Use the resource_read tool to read the content from https://raw.githubusercontent.com/microsoft/genaiscript/main/README.md`
180+
```
181+
182+
The tool automatically handles content formatting, binary detection, and multiple files.
183+
154184
## Next steps
155185

156186
Are you ready to build your own MCP tools and resources?

docs/src/content/docs/reference/cli/commands.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Options:
9393
-n, --pull-request-comment [string] create comment on a pull request with a unique id (defaults to script id)
9494
-d, --pull-request-description [string] create comment on a pull request description with a unique id (defaults to script id)
9595
-r, --pull-request-reviews create pull request reviews from annotations
96+
--issue create a GitHub issue with the generation output
9697
--teams-message Posts a message to the teams channel
9798
-j, --json emit full JSON response to output
9899
--fail-on-errors fails on detected annotation error

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ script({
264264
are a core primitive in the Model Context Protocol (MCP) that allow servers to expose data
265265
and content that can be read by clients and used as context for LLM interactions.
266266

267+
### Publishing Resources
268+
267269
In GenAIScript, you can create a resource using `host.publishResource` and
268270
it will automatically be exposed as a MCP resource.
269271

@@ -276,6 +278,30 @@ The return value is the resource uri which can be used in the prompt output.
276278

277279
The resource will be available for the lifetime of the MCP server.
278280

281+
### Reading Resources with Tools
282+
283+
GenAIScript provides built-in tools for working with resources through the `system.resources` system script:
284+
285+
- **`resource_list`**: List available resources from the host. Returns a list of available resource URIs and their descriptions.
286+
- **`resource_read`**: Read the content of a resource from a URL. Resolves various protocols and returns the content of the files found at the URL.
287+
288+
These tools support various protocols including HTTPS, file, git, gist, and VSCode URLs using the host's `resolveResource` function.
289+
290+
```js title="script-using-resources.genai.mjs"
291+
script({
292+
title: "Script that uses resource tools",
293+
system: ["system.resources"]
294+
})
295+
296+
$`Use the resource_read tool to read content from https://raw.githubusercontent.com/microsoft/genaiscript/main/README.md`
297+
```
298+
299+
The `resource_read` tool automatically handles:
300+
- **Protocol Support**: HTTPS URLs, GitHub blob URLs, Git repositories, Gists, VSCode URLs
301+
- **Content Formatting**: Returns well-formatted output with file headers and code blocks
302+
- **Binary Content**: Detects base64 encoding and provides appropriate metadata
303+
- **Multiple Files**: Supports URLs that resolve to multiple files (e.g., repository directories)
304+
279305
### Images
280306

281307
Using `env.output.image`, script can output images that will be part of the tool response.

docs/src/content/docs/reference/scripts/system.mdx

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3971,6 +3971,142 @@ export default function (ctx: ChatGenerationContext) {
39713971
`````
39723972
39733973
3974+
### `system.resources`
3975+
3976+
Read resource content from a URL using MCP resource resolution
3977+
3978+
Provides a tool that can read and return the content of resources from URLs using the host's resolveResource function. Supports various protocols including https, file, git, gist, and vscode.
3979+
3980+
- tool `resource_list`: List available resources from the host. Returns a list of available resource URIs and their descriptions.
3981+
- tool `resource_read`: Read the content of a resource from a URL. Resolves various protocols and returns the content of the files found at the URL.
3982+
3983+
`````js wrap title="system.resources"
3984+
system({
3985+
title: "Read resource content from a URL using MCP resource resolution",
3986+
description:
3987+
"Provides a tool that can read and return the content of resources from URLs using the host's resolveResource function. Supports various protocols including https, file, git, gist, and vscode.",
3988+
});
3989+
3990+
export default function (ctx: ChatGenerationContext) {
3991+
const { defTool } = ctx;
3992+
3993+
const dbg = host.logger("genaiscript:resources");
3994+
3995+
defTool(
3996+
"resource_list",
3997+
"List available resources from the host. Returns a list of available resource URIs and their descriptions.",
3998+
{
3999+
type: "object",
4000+
properties: {},
4001+
},
4002+
async (args) => {
4003+
const { context } = args;
4004+
4005+
dbg(`listing available resources`);
4006+
4007+
try {
4008+
const resources = await host.resources();
4009+
4010+
if (!resources || resources.length === 0) {
4011+
return "No resources available from host. You can still use builtin protocols like https://, file://, git://, gist:// with the resource_read tool.";
4012+
}
4013+
4014+
dbg(`found ${resources.length} resources`);
4015+
4016+
const results = resources
4017+
.map((resource) => {
4018+
const { uri, name, description, mimeType } = resource;
4019+
let result = `uri: ${uri}`;
4020+
if (name) result += `\nname: ${name}`;
4021+
if (description) result += `\ndescription: ${description}`;
4022+
if (mimeType) result += `\nmime: ${mimeType}`;
4023+
return result;
4024+
})
4025+
.join("\n\n");
4026+
4027+
context.log(`Found ${resources.length} resource(s)`);
4028+
return results;
4029+
} catch (error) {
4030+
const errorMsg = error instanceof Error ? error.message : String(error);
4031+
dbg(`error listing resources: ${errorMsg}`);
4032+
context.log(`Error listing resources: ${errorMsg}`);
4033+
return `Error listing resources: ${errorMsg}`;
4034+
}
4035+
},
4036+
);
4037+
4038+
defTool(
4039+
"resource_read",
4040+
"Read the content of a resource from a URL. Resolves various protocols and returns the content of the files found at the URL.",
4041+
{
4042+
type: "object",
4043+
properties: {
4044+
url: {
4045+
type: "string",
4046+
description:
4047+
"The URL to read the resource content from. Supports MCP resource resolution and various protocols including https, file, git, gist, and vscode.",
4048+
},
4049+
},
4050+
required: ["url"],
4051+
},
4052+
async (args) => {
4053+
const { context, url } = args;
4054+
4055+
if (!url) {
4056+
return "Error: URL is required";
4057+
}
4058+
4059+
dbg(`reading resource from URL: ${url}`);
4060+
context.log(`Reading resource content from: ${url}`);
4061+
4062+
try {
4063+
const resource = await host.resolveResource(url);
4064+
4065+
if (!resource) {
4066+
dbg(`failed to resolve resource: ${url}`);
4067+
return `Error: Unable to resolve resource from URL: ${url}`;
4068+
}
4069+
4070+
const { uri, files } = resource;
4071+
dbg(`resolved ${files.length} files from ${uri.href}`);
4072+
4073+
if (!files || files.length === 0) {
4074+
return `Error: No files found at URL: ${url}`;
4075+
}
4076+
4077+
// Return content of all files found
4078+
const results = files
4079+
.map((file) => {
4080+
if (!file.content) {
4081+
return `File: ${file.filename} (no content available)`;
4082+
}
4083+
4084+
const header = `File: ${file.filename}${file.type ? ` (${file.type})` : ""}`;
4085+
const separator = "```";
4086+
4087+
if (file.encoding === "base64") {
4088+
return `${header}\n${separator}\n[Base64 encoded content - ${file.content.length} characters]\n${separator}`;
4089+
}
4090+
4091+
return `${header}\n${separator}\n${file.content}\n${separator}`;
4092+
})
4093+
.join("\n\n");
4094+
4095+
context.log(`Successfully read ${files.length} file(s) from resource`);
4096+
return results;
4097+
} catch (error) {
4098+
const errorMsg = error instanceof Error ? error.message : String(error);
4099+
dbg(`error reading resource: ${errorMsg}`);
4100+
context.log(`Error reading resource: ${errorMsg}`);
4101+
return `Error reading resource from ${url}: ${errorMsg}`;
4102+
}
4103+
},
4104+
);
4105+
}
4106+
4107+
`````
4108+
4109+
39744110
### `system.retrieval_fuzz_search`
39754111
39764112
Full Text Fuzzy Search
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
system({
2+
title: "Read resource content from a URL using MCP resource resolution",
3+
description:
4+
"Provides a tool that can read and return the content of resources from URLs using the host's resolveResource function. Supports various protocols including https, file, git, gist, and vscode.",
5+
});
6+
7+
export default function (ctx: ChatGenerationContext) {
8+
const { defTool } = ctx;
9+
10+
const dbg = host.logger("genaiscript:resources");
11+
12+
defTool(
13+
"resource_list",
14+
"List available resources from the host. Returns a list of available resource URIs and their descriptions.",
15+
{
16+
type: "object",
17+
properties: {},
18+
},
19+
async (args) => {
20+
const { context } = args;
21+
22+
dbg(`listing available resources`);
23+
24+
try {
25+
const resources = await host.resources();
26+
27+
if (!resources || resources.length === 0) {
28+
return "No resources available from host. You can still use builtin protocols like https://, file://, git://, gist:// with the resource_read tool.";
29+
}
30+
31+
dbg(`found ${resources.length} resources`);
32+
33+
const results = resources
34+
.map((resource) => {
35+
const { uri, name, description, mimeType } = resource;
36+
let result = `uri: ${uri}`;
37+
if (name) result += `\nname: ${name}`;
38+
if (description) result += `\ndescription: ${description}`;
39+
if (mimeType) result += `\nmime: ${mimeType}`;
40+
return result;
41+
})
42+
.join("\n\n");
43+
44+
context.log(`Found ${resources.length} resource(s)`);
45+
return results;
46+
} catch (error) {
47+
const errorMsg = error instanceof Error ? error.message : String(error);
48+
dbg(`error listing resources: ${errorMsg}`);
49+
context.log(`Error listing resources: ${errorMsg}`);
50+
return `Error listing resources: ${errorMsg}`;
51+
}
52+
},
53+
);
54+
55+
defTool(
56+
"resource_read",
57+
"Read the content of a resource from a URL. Resolves various protocols and returns the content of the files found at the URL.",
58+
{
59+
type: "object",
60+
properties: {
61+
url: {
62+
type: "string",
63+
description:
64+
"The URL to read the resource content from. Supports MCP resource resolution and various protocols including https, file, git, gist, and vscode.",
65+
},
66+
},
67+
required: ["url"],
68+
},
69+
async (args) => {
70+
const { context, url } = args;
71+
72+
if (!url) {
73+
return "Error: URL is required";
74+
}
75+
76+
dbg(`reading resource from URL: ${url}`);
77+
context.log(`Reading resource content from: ${url}`);
78+
79+
try {
80+
const resource = await host.resolveResource(url);
81+
82+
if (!resource) {
83+
dbg(`failed to resolve resource: ${url}`);
84+
return `Error: Unable to resolve resource from URL: ${url}`;
85+
}
86+
87+
const { uri, files } = resource;
88+
dbg(`resolved ${files.length} files from ${uri.href}`);
89+
90+
if (!files || files.length === 0) {
91+
return `Error: No files found at URL: ${url}`;
92+
}
93+
94+
// Return content of all files found
95+
const results = files
96+
.map((file) => {
97+
if (!file.content) {
98+
return `File: ${file.filename} (no content available)`;
99+
}
100+
101+
const header = `File: ${file.filename}${file.type ? ` (${file.type})` : ""}`;
102+
const separator = "```";
103+
104+
if (file.encoding === "base64") {
105+
return `${header}\n${separator}\n[Base64 encoded content - ${file.content.length} characters]\n${separator}`;
106+
}
107+
108+
return `${header}\n${separator}\n${file.content}\n${separator}`;
109+
})
110+
.join("\n\n");
111+
112+
context.log(`Successfully read ${files.length} file(s) from resource`);
113+
return results;
114+
} catch (error) {
115+
const errorMsg = error instanceof Error ? error.message : String(error);
116+
dbg(`error reading resource: ${errorMsg}`);
117+
context.log(`Error reading resource: ${errorMsg}`);
118+
return `Error reading resource from ${url}: ${errorMsg}`;
119+
}
120+
},
121+
);
122+
}

packages/core/src/openai-chatcompletion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ export const OpenAIv1ChatCompletion: ChatCompletionHandler = async (req, cfg, op
487487
}
488488
if (cancellationToken?.isCancellationRequested) finishReason = "cancel";
489489
else if (toolCalls?.length) finishReason = "tool_calls";
490-
finishReason = finishReason || "stop"; // some provider do not implement this final mesage
490+
finishReason = finishReason || "stop"; // some provider do not implement this final message
491491
} catch (e) {
492492
finishReason = "fail";
493493
error = serializeError(e);

0 commit comments

Comments
 (0)