@xalia/agent 0.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.
Files changed (66) hide show
  1. package/.prettierrc.json +11 -0
  2. package/README.md +56 -0
  3. package/dist/agent.js +238 -0
  4. package/dist/agentUtils.js +106 -0
  5. package/dist/chat.js +296 -0
  6. package/dist/dummyLLM.js +38 -0
  7. package/dist/files.js +115 -0
  8. package/dist/iplatform.js +2 -0
  9. package/dist/llm.js +2 -0
  10. package/dist/main.js +147 -0
  11. package/dist/mcpServerManager.js +278 -0
  12. package/dist/nodePlatform.js +61 -0
  13. package/dist/openAILLM.js +38 -0
  14. package/dist/openAILLMStreaming.js +431 -0
  15. package/dist/options.js +79 -0
  16. package/dist/prompt.js +83 -0
  17. package/dist/sudoMcpServerManager.js +183 -0
  18. package/dist/test/imageLoad.test.js +14 -0
  19. package/dist/test/mcpServerManager.test.js +71 -0
  20. package/dist/test/prompt.test.js +26 -0
  21. package/dist/test/sudoMcpServerManager.test.js +49 -0
  22. package/dist/tokenAuth.js +39 -0
  23. package/dist/tools.js +44 -0
  24. package/eslint.config.mjs +25 -0
  25. package/frog.png +0 -0
  26. package/package.json +42 -0
  27. package/scripts/git_message +31 -0
  28. package/scripts/git_wip +21 -0
  29. package/scripts/pr_message +18 -0
  30. package/scripts/pr_review +16 -0
  31. package/scripts/sudomcp_import +23 -0
  32. package/scripts/test_script +60 -0
  33. package/src/agent.ts +283 -0
  34. package/src/agentUtils.ts +198 -0
  35. package/src/chat.ts +346 -0
  36. package/src/dummyLLM.ts +50 -0
  37. package/src/files.ts +95 -0
  38. package/src/iplatform.ts +17 -0
  39. package/src/llm.ts +15 -0
  40. package/src/main.ts +187 -0
  41. package/src/mcpServerManager.ts +371 -0
  42. package/src/nodePlatform.ts +24 -0
  43. package/src/openAILLM.ts +51 -0
  44. package/src/openAILLMStreaming.ts +528 -0
  45. package/src/options.ts +103 -0
  46. package/src/prompt.ts +93 -0
  47. package/src/sudoMcpServerManager.ts +278 -0
  48. package/src/test/imageLoad.test.ts +14 -0
  49. package/src/test/mcpServerManager.test.ts +98 -0
  50. package/src/test/prompt.test.src +0 -0
  51. package/src/test/prompt.test.ts +26 -0
  52. package/src/test/sudoMcpServerManager.test.ts +65 -0
  53. package/src/tokenAuth.ts +50 -0
  54. package/src/tools.ts +57 -0
  55. package/test_data/background_test_profile.json +6 -0
  56. package/test_data/background_test_script.json +11 -0
  57. package/test_data/dummyllm_script_simplecalc.json +28 -0
  58. package/test_data/git_message_profile.json +4 -0
  59. package/test_data/git_wip_system.txt +5 -0
  60. package/test_data/pr_message_profile.json +4 -0
  61. package/test_data/pr_review_profile.json +4 -0
  62. package/test_data/prompt_simplecalc.txt +1 -0
  63. package/test_data/simplecalc_profile.json +4 -0
  64. package/test_data/sudomcp_import_profile.json +4 -0
  65. package/test_data/test_script_profile.json +8 -0
  66. package/tsconfig.json +13 -0
package/src/prompt.ts ADDED
@@ -0,0 +1,93 @@
1
+ import readline from "readline";
2
+
3
+ const DEFAULT_PROMPT: string = "USER: ";
4
+
5
+ export class Prompt {
6
+ private line: string | undefined;
7
+ private online: { (line: string | undefined): void } | undefined;
8
+ // private onerror: { (reason?: any): void } | undefined;
9
+ private prompt: readline.Interface;
10
+
11
+ constructor() {
12
+ this.prompt = readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout,
15
+ prompt: DEFAULT_PROMPT,
16
+ });
17
+
18
+ this.prompt.on("line", (line) => {
19
+ this.line = line;
20
+ this.resolve();
21
+ });
22
+ this.prompt.on("close", () => {
23
+ this.line = undefined;
24
+ this.resolve();
25
+ });
26
+ }
27
+
28
+ async run(prompt?: string): Promise<string | undefined> {
29
+ // Clear any line
30
+ this.line = "";
31
+
32
+ return new Promise<string | undefined>((r) => {
33
+ this.online = r;
34
+ if (prompt) {
35
+ this.prompt.setPrompt(prompt);
36
+ }
37
+ this.prompt.prompt();
38
+ if (prompt) {
39
+ this.prompt.setPrompt(DEFAULT_PROMPT);
40
+ }
41
+ });
42
+ }
43
+
44
+ shutdown(): void {
45
+ this.prompt.close();
46
+ }
47
+
48
+ private resolve(): void {
49
+ if (this.online) {
50
+ this.online(this.line);
51
+ }
52
+ this.online = undefined;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Support prompts:
58
+ * - some text (msg: some text, cmds: undefined)
59
+ * - :i image.png some text (msg: some text, cmds: ["i", "image.png"])
60
+ * - :i image.png (msg: undefined, cmds: ["i", "image.png"])
61
+ * - :l (msg: undefined, cmds: ["l"])
62
+ * - :e toolName .. (msg: undefined, cmds: ["e", "toolName", ...])
63
+ * - :ea toolName .. (msg: undefined, cmds: ["ea", "toolName", ...])
64
+ */
65
+ export function parsePrompt(prompt: string): {
66
+ msg?: string;
67
+ cmds?: string[];
68
+ } {
69
+ prompt = prompt.trim();
70
+
71
+ let msg: string | undefined = undefined;
72
+ let cmds: string[] | undefined = undefined;
73
+
74
+ if (prompt.startsWith(":") || prompt.startsWith("/")) {
75
+ cmds = prompt.split(" ");
76
+ cmds[0] = cmds[0].slice(1);
77
+
78
+ if (cmds[0] == "i") {
79
+ // :i is special as it may have a trailing message
80
+ const fileDelim = prompt.indexOf(" ", 3);
81
+ if (fileDelim < 0) {
82
+ cmds = [cmds[0], prompt.slice(3)];
83
+ } else {
84
+ msg = prompt.slice(fileDelim + 1);
85
+ cmds = [cmds[0], prompt.slice(3, fileDelim)];
86
+ }
87
+ }
88
+ } else {
89
+ msg = prompt;
90
+ }
91
+
92
+ return { msg, cmds };
93
+ }
@@ -0,0 +1,278 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import { McpServerManager, McpServerSettings } from "./mcpServerManager";
3
+ import {
4
+ ApiClient,
5
+ McpServerBrief,
6
+ DEFAULT_SERVER_URL,
7
+ getLogger,
8
+ AuthenticationRequired,
9
+ EnabledToolsList,
10
+ McpServer,
11
+ } from "@xalia/xmcp/sdk";
12
+ import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
13
+
14
+ const logger = getLogger();
15
+
16
+ export const LOCAL_SERVER_URL: string = "http://localhost:5001";
17
+
18
+ /**
19
+ * ServerBrief with sanitized name, and original name in another field.
20
+ */
21
+ class SanitizedServerBrief extends McpServerBrief {
22
+ readonly originalName: string;
23
+
24
+ private constructor() {
25
+ super("dummy", "dummy", "dummy", false, {}, "dummy", "dummy");
26
+ this.originalName = "dummy";
27
+ }
28
+
29
+ public static fromServerBrief(brief: McpServerBrief): SanitizedServerBrief {
30
+ const b = brief as unknown;
31
+ const origName = brief.name;
32
+ (b as any).originalName = origName; // eslint-disable-line
33
+ brief.name = sanitizeName(origName);
34
+ return b as SanitizedServerBrief;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Manages access to the catalogue of servers hosted by sudomcp. Supports
40
+ * adding these servers to McpServerManager.
41
+ */
42
+ export class SudoMcpServerManager {
43
+ private constructor(
44
+ private mcpServerManager: McpServerManager,
45
+ private apiClient: ApiClient,
46
+ private serverBriefs: SanitizedServerBrief[],
47
+ private serverBriefsMap: { [serverName: string]: SanitizedServerBrief },
48
+ private toolCache: { [serverName: string]: Tool[] },
49
+ private openUrl: (
50
+ url: string,
51
+ authResultP: Promise<boolean>,
52
+ displayName: string
53
+ ) => void,
54
+ // Redirect to this page after successful authorization
55
+ private authorized_url: string | undefined
56
+ ) {}
57
+
58
+ /**
59
+ * Initialize an ApiClient to interface with SudoMCP backend and
60
+ * fetch the current list of ServerBriefs.
61
+ */
62
+ public static async initialize(
63
+ mcpServerManager: McpServerManager,
64
+ openUrl: (
65
+ url: string,
66
+ authResultP: Promise<boolean>,
67
+ displayName: string
68
+ ) => void,
69
+ sudoMcpUrl?: string,
70
+ sudoMcpApiKey?: string,
71
+ authorized_url?: string
72
+ ): Promise<SudoMcpServerManager> {
73
+ // TODO: Keep it on here and pass to `McpServerManager.addMcpServer`
74
+ const apiClient = new ApiClient(
75
+ sudoMcpUrl ?? DEFAULT_SERVER_URL,
76
+ sudoMcpApiKey ?? "dummy_key"
77
+ );
78
+ // Fetch server list
79
+ const servers = await apiClient.listServers();
80
+
81
+ const [mcpServers, mcpServersMap] = buildServersList(servers);
82
+ return new SudoMcpServerManager(
83
+ mcpServerManager,
84
+ apiClient,
85
+ mcpServers,
86
+ mcpServersMap,
87
+ {},
88
+ openUrl,
89
+ authorized_url
90
+ );
91
+ }
92
+
93
+ /// TODO: Bit awkward that we have to restore via this class, but it's the
94
+ /// only class which knows how to restore (restart) the mcp servers.
95
+ public async restoreMcpSettings(
96
+ mcpSettings: McpServerSettings
97
+ ): Promise<void> {
98
+ await this.restoreConfiguration(mcpSettings);
99
+ }
100
+
101
+ /**
102
+ * Load the configuration from sudomcp, and enable the specified tools.
103
+ */
104
+ public async restoreConfiguration(
105
+ mcpConfig: McpServerSettings
106
+ ): Promise<void> {
107
+ logger.debug("Restoring Mcp config");
108
+
109
+ // Concurrently establish all server connections
110
+ const addServer = async (serverName: string) => {
111
+ logger.debug(`restoring ${serverName} ...`);
112
+ return this.addMcpServer(serverName);
113
+ };
114
+ await Promise.all(Object.entries(mcpConfig).map((e) => addServer(e[0])));
115
+
116
+ // Enable tools
117
+ const enableTools = ([serverName, enabled]: [string, EnabledToolsList]) => {
118
+ // Migrate the old style: { [tool: string]: true } to the new list format
119
+ if (!(enabled instanceof Array)) {
120
+ enabled = Object.keys(enabled);
121
+ }
122
+ if (enabled.length === 0) {
123
+ logger.debug(` restoring "${serverName}": (all tools)`);
124
+ this.mcpServerManager.enableAllTools(serverName);
125
+ return;
126
+ }
127
+
128
+ logger.debug(` restoring "${serverName}": ${JSON.stringify(enabled)}`);
129
+ for (const toolName of enabled) {
130
+ this.mcpServerManager.enableTool(serverName, toolName);
131
+ }
132
+ };
133
+ Object.entries(mcpConfig).map((e) => enableTools(e));
134
+ }
135
+
136
+ /**
137
+ * Query backend for server list, clear tool cache.
138
+ */
139
+ public async refresh(): Promise<void> {
140
+ const servers = await this.apiClient.listServers();
141
+ const [mcpServers, mcpServersMap] = buildServersList(servers);
142
+ this.serverBriefs = mcpServers;
143
+ this.serverBriefsMap = mcpServersMap;
144
+ this.toolCache = {};
145
+ }
146
+
147
+ public getServerBriefs(): McpServerBrief[] {
148
+ return this.serverBriefs;
149
+ }
150
+
151
+ public getMcpServerManager(): McpServerManager {
152
+ return this.mcpServerManager;
153
+ }
154
+
155
+ /**
156
+ * Return tool list for a given MCP server. Queries the backend
157
+ * if necessary and caches the result.
158
+ */
159
+ public async getServerTools(serverName: string): Promise<Tool[]> {
160
+ // Check cache
161
+ let tools = this.toolCache[serverName];
162
+ if (tools) {
163
+ return tools;
164
+ }
165
+
166
+ // Query backend (using the original name)
167
+ const originalName = this.serverBriefsMap[serverName].originalName;
168
+ tools = await this.apiClient.listTools(originalName);
169
+ this.toolCache[serverName] = tools;
170
+ return tools;
171
+ }
172
+
173
+ /**
174
+ * Add a server to the `McpServerManager`, using `ApiClient`
175
+ * to produce the transport. Validates the server's config
176
+ * schema, if applicable.
177
+ */
178
+ public async addMcpServer(serverName: string): Promise<void> {
179
+ const tools = await this.getServerTools(serverName);
180
+ const originalName = this.serverBriefsMap[serverName].originalName;
181
+ const mcpserver = await this.apiClient.getDetails(originalName, "run");
182
+ const client = new McpClient({
183
+ name: "@xalia/agent",
184
+ version: "1.0.0",
185
+ });
186
+ await connectServer(
187
+ client,
188
+ this.apiClient,
189
+ mcpserver,
190
+ this.openUrl,
191
+ this.authorized_url
192
+ );
193
+ await this.mcpServerManager.addMcpServerWithClient(
194
+ client,
195
+ serverName,
196
+ tools
197
+ );
198
+ }
199
+
200
+ public getOriginalName(serverName: string): string {
201
+ return this.serverBriefsMap[serverName].name;
202
+ }
203
+
204
+ public getApiClient(): ApiClient {
205
+ return this.apiClient;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Connect a client to a hosted MCP server session,
211
+ * prompting for authentication if needed.
212
+ */
213
+ async function connectServer(
214
+ client: McpClient,
215
+ apiClient: ApiClient,
216
+ mcpServer: McpServer,
217
+ openUrl: (
218
+ url: string,
219
+ authResultP: Promise<boolean>,
220
+ displayName: string
221
+ ) => void,
222
+ authorized_url?: string,
223
+ noRetry: boolean = false
224
+ ): Promise<void> {
225
+ const transport = apiClient.mcpSession(mcpServer);
226
+
227
+ await client.connect(transport).catch(async (e) => {
228
+ if (e instanceof AuthenticationRequired && !noRetry) {
229
+ logger.info("authentication required: " + e.msg);
230
+
231
+ const { url, authenticatedP } = await apiClient.authenticate(
232
+ mcpServer.name,
233
+ authorized_url
234
+ );
235
+ logger.info(`authenticate at url: ${url}`);
236
+ openUrl(url, authenticatedP, mcpServer.title);
237
+ const authResult = await authenticatedP;
238
+ logger.info(`authResult: ${authResult}`);
239
+ if (!authResult) {
240
+ throw "authentication failed";
241
+ }
242
+ return connectServer(
243
+ client,
244
+ apiClient,
245
+ mcpServer,
246
+ openUrl,
247
+ authorized_url,
248
+ true
249
+ );
250
+ } else {
251
+ throw e;
252
+ }
253
+ });
254
+ }
255
+
256
+ /**
257
+ * Given a list of`ServerBrief` objects, create the corresponding list and map
258
+ * of `SantiizedServerBrief` objects (namely, objects with sanitized names,
259
+ * holding the original name in a new field).
260
+ */
261
+ function buildServersList(
262
+ serverList: McpServerBrief[]
263
+ ): [SanitizedServerBrief[], { [serverName: string]: SanitizedServerBrief }] {
264
+ const servers: SanitizedServerBrief[] = [];
265
+ const serversMap: { [serverName: string]: SanitizedServerBrief } = {};
266
+
267
+ for (const s of serverList) {
268
+ const ss = SanitizedServerBrief.fromServerBrief(s);
269
+ servers.push(ss);
270
+ serversMap[ss.name] = ss;
271
+ }
272
+
273
+ return [servers, serversMap];
274
+ }
275
+
276
+ function sanitizeName(name: string): string {
277
+ return name.replace("/", "_");
278
+ }
@@ -0,0 +1,14 @@
1
+ import { expect } from "chai";
2
+ import { loadImageB64OrUndefined } from "../files";
3
+ import { strict as assert } from "assert";
4
+
5
+ describe("Image loading", () => {
6
+ it("convert to data url", async function () {
7
+ const imageB64 = loadImageB64OrUndefined("frog.png");
8
+ // console.log(`imageB64: ${imageB64}`);
9
+
10
+ expect(imageB64).to.be.a("string");
11
+ assert(typeof imageB64 === "string");
12
+ expect(imageB64.startsWith("data:image/png;base64")).eql(true);
13
+ });
14
+ });
@@ -0,0 +1,98 @@
1
+ import { expect } from "chai";
2
+ import {
3
+ computeQualifiedName,
4
+ McpServerManager,
5
+ splitQualifiedName,
6
+ } from "../mcpServerManager";
7
+
8
+ // Handle automatically closing at the end of tests.
9
+
10
+ let managers: { [name: string]: McpServerManager } = {};
11
+ let idx = 0;
12
+
13
+ function getMcpServerManager(): McpServerManager {
14
+ const tm = new McpServerManager();
15
+ const name = "mgr" + ++idx;
16
+ managers[name] = tm;
17
+ return tm;
18
+ }
19
+
20
+ async function shutdownAll() {
21
+ for (const [k, v] of Object.entries(managers)) {
22
+ console.log(`shutting down ${k}`);
23
+ await v.shutdown();
24
+ }
25
+ managers = {};
26
+ }
27
+
28
+ describe("McpServerManager", async () => {
29
+ it("should initialize with correct descriptions", async (): Promise<void> => {
30
+ const tm = getMcpServerManager();
31
+ await tm.addMcpServer(
32
+ "simplecalc",
33
+ "http://localhost:5001/mcpservers/sudomcp/simplecalc/session",
34
+ "dummy_key"
35
+ );
36
+
37
+ const server = tm.getMcpServer("simplecalc");
38
+ const availableTools = server.getTools();
39
+ expect(availableTools.length).greaterThan(0);
40
+
41
+ // Initially, no tools are enabled.
42
+ expect(tm.getOpenAITools().length).equal(0);
43
+
44
+ // Enable a tool and check we get a description for it.
45
+ const toolName = availableTools[0].name;
46
+ {
47
+ tm.enableTool("simplecalc", toolName);
48
+ const tools = tm.getOpenAITools();
49
+ expect(tools.length).greaterThan(0);
50
+ console.log(`tools: ${JSON.stringify(tools, undefined, 2)}`);
51
+ }
52
+
53
+ // Invoke
54
+ {
55
+ const qualifiedName = tm.getOpenAITools()[0].function.name;
56
+ const r = await tm.invoke(qualifiedName, { a: 1000, b: 10 });
57
+ console.log(`response: ${r}`);
58
+ }
59
+
60
+ // Disable and check we don't get the spec anymore
61
+ {
62
+ tm.disableTool("simplecalc", toolName);
63
+ const tools = tm.getOpenAITools();
64
+ expect(tools.length).equal(0);
65
+ }
66
+ }).timeout(10000);
67
+
68
+ it("qualified names", async () => {
69
+ const serverName = "server";
70
+ const toolName = "tool";
71
+ const qualifiedName = computeQualifiedName(serverName, toolName);
72
+
73
+ const [_serverName, _toolName] = splitQualifiedName(qualifiedName);
74
+
75
+ expect(_serverName).eql(serverName);
76
+ expect(_toolName).eql(toolName);
77
+ });
78
+
79
+ it("add / remove servers", async () => {
80
+ const tm = getMcpServerManager();
81
+
82
+ await tm.addMcpServer(
83
+ "simplecalc",
84
+ "http://localhost:5001/mcpservers/sudomcp/simplecalc/session",
85
+ "dummy_key"
86
+ );
87
+ tm.enableAllTools("simplecalc");
88
+ expect(tm.getOpenAITools().length).greaterThan(0);
89
+
90
+ // Remove server and check there is no server, and no openai entries.
91
+
92
+ await tm.removeMcpServer("simplecalc");
93
+ expect(tm.getOpenAITools().length).equal(0);
94
+ expect(() => {
95
+ tm.getMcpServer("simplecalc");
96
+ }).throws();
97
+ });
98
+ }).afterAll(shutdownAll);
File without changes
@@ -0,0 +1,26 @@
1
+ import { expect } from "chai";
2
+ import { parsePrompt } from "../prompt";
3
+
4
+ describe("Prompt", () => {
5
+ it("should recognise commands", async function () {
6
+ const expectedResults = {
7
+ "some text ": { msg: "some text", cmds: undefined },
8
+ ":i image.png some text": { msg: "some text", cmds: ["i", "image.png"] },
9
+ ":i image.png": { msg: undefined, cmds: ["i", "image.png"] },
10
+ ":l": { msg: undefined, cmds: ["l"] },
11
+ ":e serverName toolName": {
12
+ msg: undefined,
13
+ cmds: ["e", "serverName", "toolName"],
14
+ },
15
+ ":ea serverName": {
16
+ msg: undefined,
17
+ cmds: ["ea", "serverName"],
18
+ },
19
+ };
20
+
21
+ for (const [prompt, expected] of Object.entries(expectedResults)) {
22
+ const res = parsePrompt(prompt);
23
+ expect(res).eql(expected);
24
+ }
25
+ });
26
+ });
@@ -0,0 +1,65 @@
1
+ import { expect } from "chai";
2
+ import { McpServerManager } from "../mcpServerManager";
3
+ import {
4
+ SudoMcpServerManager,
5
+ LOCAL_SERVER_URL,
6
+ } from "../sudoMcpServerManager";
7
+ import chalk from "chalk";
8
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
9
+
10
+ let managers: [McpServerManager, SudoMcpServerManager] | undefined = undefined;
11
+
12
+ async function getServerManagers(): Promise<
13
+ [McpServerManager, SudoMcpServerManager]
14
+ > {
15
+ if (!managers) {
16
+ const tm = new McpServerManager();
17
+ const sm = await SudoMcpServerManager.initialize(
18
+ tm,
19
+ (_url: string) => {
20
+ throw "unexpected call to openUrl";
21
+ },
22
+ LOCAL_SERVER_URL,
23
+ "dummy_key"
24
+ );
25
+
26
+ managers = [tm, sm];
27
+ }
28
+
29
+ return managers;
30
+ }
31
+
32
+ async function shutdownAll() {
33
+ if (managers) {
34
+ await managers[0].shutdown();
35
+ }
36
+ managers = undefined;
37
+ }
38
+
39
+ describe("SudoMcpServerManager", async () => {
40
+ it("should fetch servers from deployed backend", async () => {
41
+ const [, sm] = await getServerManagers();
42
+ const serverBriefs = sm.getServerBriefs();
43
+ expect(serverBriefs.length).greaterThan(0);
44
+ }).timeout(10000);
45
+
46
+ it("should fetch tools for a server", async () => {
47
+ const [, sm] = await getServerManagers();
48
+ const tools = await sm.getServerTools("sudomcp_simplecalc");
49
+ expect(tools.length).greaterThan(0);
50
+ }).timeout(10000);
51
+
52
+ it("should add a new MCP server", async () => {
53
+ const [, sm] = await getServerManagers();
54
+ await sm.addMcpServer("sudomcp_simplecalc");
55
+ const serverBriefs = sm.getServerBriefs();
56
+ expect(
57
+ serverBriefs.some((brief) => brief.name === "sudomcp_simplecalc")
58
+ ).eql(true);
59
+ }).timeout(20000);
60
+ }).afterAll(shutdownAll);
61
+
62
+ export function prettyPrintTool(tool: Tool): void {
63
+ console.log(`- ${chalk.green(tool.name)}:`);
64
+ console.log(` ${chalk.italic(tool.description)}\n`);
65
+ }
@@ -0,0 +1,50 @@
1
+ import {
2
+ OAuthClientInformationFull,
3
+ OAuthClientMetadata,
4
+ OAuthTokens,
5
+ } from "@modelcontextprotocol/sdk/shared/auth.js";
6
+ import { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
7
+
8
+ export class TokenAuth implements OAuthClientProvider {
9
+ token: string;
10
+
11
+ constructor(token: string) {
12
+ this.token = token;
13
+ }
14
+
15
+ get redirectUrl(): string {
16
+ throw "unimpl";
17
+ }
18
+ get clientMetadata(): OAuthClientMetadata {
19
+ throw "unimpl";
20
+ }
21
+
22
+ clientInformation(): undefined {
23
+ return undefined;
24
+ }
25
+
26
+ saveClientInformation?(
27
+ _clientInformation: OAuthClientInformationFull
28
+ ): void | Promise<void> {
29
+ throw "unimpl";
30
+ }
31
+
32
+ tokens(): OAuthTokens | undefined | Promise<OAuthTokens | undefined> {
33
+ return {
34
+ access_token: this.token,
35
+ token_type: "bearer",
36
+ };
37
+ }
38
+ saveTokens(_tokens: OAuthTokens): void | Promise<void> {
39
+ throw "unimpl";
40
+ }
41
+ redirectToAuthorization(_authorizationUrl: URL): void | Promise<void> {
42
+ throw "unimpl";
43
+ }
44
+ saveCodeVerifier(_codeVerifier: string): void | Promise<void> {
45
+ throw "unimpl";
46
+ }
47
+ codeVerifier(): string | Promise<string> {
48
+ throw "unimpl";
49
+ }
50
+ }
package/src/tools.ts ADDED
@@ -0,0 +1,57 @@
1
+ import OpenAI from "openai";
2
+
3
+ export const temperatureTool: OpenAI.ChatCompletionTool = {
4
+ type: "function",
5
+ function: {
6
+ name: "getCurrentTemperature",
7
+ description: "Get the current temperature for a specific location",
8
+ parameters: {
9
+ type: "object",
10
+ properties: {
11
+ location: {
12
+ type: "string",
13
+ description: "The city and state, e.g., San Francisco, CA",
14
+ },
15
+ unit: {
16
+ type: "string",
17
+ enum: ["Celsius", "Fahrenheit"],
18
+ description:
19
+ "The temperature unit to use. Infer this from the user's location.",
20
+ },
21
+ },
22
+ required: ["location", "unit"],
23
+ },
24
+ },
25
+ };
26
+
27
+ interface TemperatureArgs {
28
+ location?: string;
29
+ unit?: string;
30
+ }
31
+
32
+ function isTemperatureArgs(args: unknown): args is TemperatureArgs {
33
+ return (
34
+ typeof args === "object" &&
35
+ args !== null &&
36
+ ("location" in args || "unit" in args)
37
+ );
38
+ }
39
+
40
+ // Handle non-MCP tools
41
+ export const toolCallbacks: {
42
+ [toolName: string]: { (args: unknown): string };
43
+ } = {
44
+ getCurrentTemperature: (args: unknown) => {
45
+ if (!isTemperatureArgs(args) || !args.location) {
46
+ return `Location required`;
47
+ }
48
+ return `The temperature in ${args.location} is 22 degrees ${args.unit}`;
49
+ },
50
+ };
51
+
52
+ export function displayToolCall(
53
+ toolCall: OpenAI.ChatCompletionMessageToolCall
54
+ ): void {
55
+ console.log(`AGENT: Tool Call: ${toolCall.function.name}`);
56
+ console.log(` Args: ${toolCall.function.arguments}`);
57
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "NOTES": "Intended to be used by a script running in ../../mcppro/_test_background",
3
+ "model": "dummy:../../agent/test_data/background_test_script.json",
4
+ "system_prompt": "A test system prompt",
5
+ "mcp_settings": {}
6
+ }