@xalia/agent 1.0.19
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/.prettierrc.json +11 -0
- package/README.md +57 -0
- package/dist/agent.js +278 -0
- package/dist/agentUtils.js +88 -0
- package/dist/chat.js +278 -0
- package/dist/dummyLLM.js +28 -0
- package/dist/files.js +115 -0
- package/dist/iplatform.js +2 -0
- package/dist/llm.js +2 -0
- package/dist/main.js +136 -0
- package/dist/mcpServerManager.js +269 -0
- package/dist/nodePlatform.js +61 -0
- package/dist/openAILLM.js +31 -0
- package/dist/options.js +79 -0
- package/dist/prompt.js +83 -0
- package/dist/sudoMcpServerManager.js +174 -0
- package/dist/test/imageLoad.test.js +14 -0
- package/dist/test/mcpServerManager.test.js +71 -0
- package/dist/test/prompt.test.js +26 -0
- package/dist/test/sudoMcpServerManager.test.js +49 -0
- package/dist/tokenAuth.js +39 -0
- package/dist/tools.js +44 -0
- package/eslint.config.mjs +25 -0
- package/frog.png +0 -0
- package/package.json +41 -0
- package/scripts/git_message +31 -0
- package/scripts/git_wip +21 -0
- package/scripts/pr_message +18 -0
- package/scripts/pr_review +16 -0
- package/scripts/sudomcp_import +23 -0
- package/scripts/test_script +60 -0
- package/src/agent.ts +357 -0
- package/src/agentUtils.ts +188 -0
- package/src/chat.ts +325 -0
- package/src/dummyLLM.ts +36 -0
- package/src/files.ts +95 -0
- package/src/iplatform.ts +11 -0
- package/src/llm.ts +12 -0
- package/src/main.ts +171 -0
- package/src/mcpServerManager.ts +365 -0
- package/src/nodePlatform.ts +24 -0
- package/src/openAILLM.ts +43 -0
- package/src/options.ts +103 -0
- package/src/prompt.ts +93 -0
- package/src/sudoMcpServerManager.ts +268 -0
- package/src/test/imageLoad.test.ts +14 -0
- package/src/test/mcpServerManager.test.ts +98 -0
- package/src/test/prompt.test.src +0 -0
- package/src/test/prompt.test.ts +26 -0
- package/src/test/sudoMcpServerManager.test.ts +63 -0
- package/src/tokenAuth.ts +50 -0
- package/src/tools.ts +57 -0
- package/test_data/background_test_profile.json +7 -0
- package/test_data/background_test_script.json +11 -0
- package/test_data/dummyllm_script_simplecalc.json +28 -0
- package/test_data/git_message_profile.json +4 -0
- package/test_data/git_wip_system.txt +5 -0
- package/test_data/pr_message_profile.json +4 -0
- package/test_data/pr_review_profile.json +4 -0
- package/test_data/prompt_simplecalc.txt +1 -0
- package/test_data/simplecalc_profile.json +4 -0
- package/test_data/sudomcp_import_profile.json +4 -0
- package/test_data/test_script_profile.json +9 -0
- package/tsconfig.json +13 -0
@@ -0,0 +1,268 @@
|
|
1
|
+
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
2
|
+
import { McpServerManager, McpServerManagerSettings } from "./mcpServerManager";
|
3
|
+
import {
|
4
|
+
ApiClient,
|
5
|
+
McpServerBrief,
|
6
|
+
DEFAULT_SERVER_URL,
|
7
|
+
McpServerConfiguration,
|
8
|
+
getLogger,
|
9
|
+
verifyConfigForServer,
|
10
|
+
AuthenticationRequired,
|
11
|
+
McpServer,
|
12
|
+
} from "@xalia/xmcp/sdk";
|
13
|
+
import { strict as assert } from "assert";
|
14
|
+
import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
|
15
|
+
|
16
|
+
const logger = getLogger();
|
17
|
+
|
18
|
+
export const LOCAL_SERVER_URL: string = "http://localhost:5001";
|
19
|
+
|
20
|
+
/**
|
21
|
+
* ServerBrief with sanitized name, and original name in another field.
|
22
|
+
*/
|
23
|
+
class SanitizedServerBrief extends McpServerBrief {
|
24
|
+
readonly originalName: string;
|
25
|
+
|
26
|
+
private constructor() {
|
27
|
+
super("dummy", "dummy", "dummy", false, {}, "dummy", "dummy");
|
28
|
+
this.originalName = "dummy";
|
29
|
+
}
|
30
|
+
|
31
|
+
public static fromServerBrief(brief: McpServerBrief): SanitizedServerBrief {
|
32
|
+
const b = brief as unknown;
|
33
|
+
const origName = brief.name;
|
34
|
+
(b as any).originalName = origName; // eslint-disable-line
|
35
|
+
brief.name = sanitizeName(origName);
|
36
|
+
return b as SanitizedServerBrief;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Manages access to the catalogue of servers hosted by sudomcp. Supports
|
42
|
+
* adding these servers to McpServerManager.
|
43
|
+
*/
|
44
|
+
export class SudoMcpServerManager {
|
45
|
+
private constructor(
|
46
|
+
private mcpServerManager: McpServerManager,
|
47
|
+
private apiClient: ApiClient,
|
48
|
+
private serverBriefs: SanitizedServerBrief[],
|
49
|
+
private serverBriefsMap: { [serverName: string]: SanitizedServerBrief },
|
50
|
+
private toolCache: { [serverName: string]: Tool[] },
|
51
|
+
private openUrl: (url: string) => void,
|
52
|
+
// Redirect to this page after successful authorization
|
53
|
+
private authorized_url: string | undefined
|
54
|
+
) {}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Initialize an ApiClient to interface with SudoMCP backend and
|
58
|
+
* fetch the current list of ServerBriefs.
|
59
|
+
*/
|
60
|
+
public static async initialize(
|
61
|
+
mcpServerManager: McpServerManager,
|
62
|
+
openUrl: (url: string) => void,
|
63
|
+
sudoMcpUrl?: string,
|
64
|
+
sudoMcpApiKey?: string,
|
65
|
+
authorized_url?: string
|
66
|
+
): Promise<SudoMcpServerManager> {
|
67
|
+
// TODO: Keep it on here and pass to `McpServerManager.addMcpServer`
|
68
|
+
const apiClient = new ApiClient(
|
69
|
+
sudoMcpUrl ?? DEFAULT_SERVER_URL,
|
70
|
+
sudoMcpApiKey ?? "dummy_key"
|
71
|
+
);
|
72
|
+
// Fetch server list
|
73
|
+
const servers = await apiClient.search([]);
|
74
|
+
|
75
|
+
const [mcpServers, mcpServersMap] = buildServersList(servers);
|
76
|
+
return new SudoMcpServerManager(
|
77
|
+
mcpServerManager,
|
78
|
+
apiClient,
|
79
|
+
mcpServers,
|
80
|
+
mcpServersMap,
|
81
|
+
{},
|
82
|
+
openUrl,
|
83
|
+
authorized_url
|
84
|
+
);
|
85
|
+
}
|
86
|
+
|
87
|
+
/// TODO: Bit awkward that we have to restore via this class, but it's the
|
88
|
+
/// only class which knows how to restore (restart) the mcp servers.
|
89
|
+
public async restoreMcpSettings(
|
90
|
+
mcpSettings: McpServerManagerSettings,
|
91
|
+
serverConfigs: { [serverName: string]: McpServerConfiguration }
|
92
|
+
): Promise<void> {
|
93
|
+
await this.restoreConfiguration(mcpSettings, serverConfigs);
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Load the configuration from sudomcp, and enable the specified tools.
|
98
|
+
*/
|
99
|
+
public async restoreConfiguration(
|
100
|
+
mcpConfig: McpServerManagerSettings,
|
101
|
+
serverConfigs: { [serverName: string]: McpServerConfiguration }
|
102
|
+
): Promise<void> {
|
103
|
+
logger.debug("Restoring Mcp config");
|
104
|
+
|
105
|
+
// TODO: remove existing servers?
|
106
|
+
|
107
|
+
for (const [serverName, enabled] of Object.entries(mcpConfig)) {
|
108
|
+
logger.debug(` restoring "${serverName}" ...`);
|
109
|
+
|
110
|
+
if (Object.keys(enabled).length === 0) {
|
111
|
+
logger.debug(` restoring "${serverName}": (empty)`);
|
112
|
+
continue;
|
113
|
+
}
|
114
|
+
|
115
|
+
const serverConfig = serverConfigs[serverName] ?? {};
|
116
|
+
logger.debug(
|
117
|
+
` restoring ${serverName}: ${JSON.stringify(Object.keys(enabled))}`
|
118
|
+
);
|
119
|
+
await this.addMcpServer(serverName, serverConfig);
|
120
|
+
for (const [toolName, v] of Object.entries(enabled)) {
|
121
|
+
assert(v === true);
|
122
|
+
this.mcpServerManager.enableTool(serverName, toolName);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
/**
|
128
|
+
* Query backend for server list, clear tool cache.
|
129
|
+
*/
|
130
|
+
public async refresh(): Promise<void> {
|
131
|
+
const servers = await this.apiClient.search([]);
|
132
|
+
const [mcpServers, mcpServersMap] = buildServersList(servers);
|
133
|
+
this.serverBriefs = mcpServers;
|
134
|
+
this.serverBriefsMap = mcpServersMap;
|
135
|
+
this.toolCache = {};
|
136
|
+
}
|
137
|
+
|
138
|
+
public getServerBriefs(): McpServerBrief[] {
|
139
|
+
return this.serverBriefs;
|
140
|
+
}
|
141
|
+
|
142
|
+
public getMcpServerManager(): McpServerManager {
|
143
|
+
return this.mcpServerManager;
|
144
|
+
}
|
145
|
+
|
146
|
+
/**
|
147
|
+
* Return tool list for a given MCP server. Queries the backend
|
148
|
+
* if necessary and caches the result.
|
149
|
+
*/
|
150
|
+
public async getServerTools(serverName: string): Promise<Tool[]> {
|
151
|
+
// Check cache
|
152
|
+
let tools = this.toolCache[serverName];
|
153
|
+
if (tools) {
|
154
|
+
return tools;
|
155
|
+
}
|
156
|
+
|
157
|
+
// Query backend (using the original name)
|
158
|
+
const originalName = this.serverBriefsMap[serverName].originalName;
|
159
|
+
tools = await this.apiClient.listTools(originalName);
|
160
|
+
this.toolCache[serverName] = tools;
|
161
|
+
return tools;
|
162
|
+
}
|
163
|
+
|
164
|
+
/**
|
165
|
+
* Add a server to the `McpServerManager`, using `ApiClient`
|
166
|
+
* to produce the transport. Validates the server's config
|
167
|
+
* schema, if applicable.
|
168
|
+
*/
|
169
|
+
public async addMcpServer(
|
170
|
+
serverName: string,
|
171
|
+
configuration: McpServerConfiguration | undefined
|
172
|
+
): Promise<void> {
|
173
|
+
const tools = await this.getServerTools(serverName);
|
174
|
+
const originalName = this.serverBriefsMap[serverName].originalName;
|
175
|
+
const mcpserver = await this.apiClient.getDetails(originalName, "run");
|
176
|
+
verifyConfigForServer(mcpserver, configuration);
|
177
|
+
const client = new McpClient({
|
178
|
+
name: "@xalia/agent",
|
179
|
+
version: "1.0.0",
|
180
|
+
});
|
181
|
+
await connectServer(
|
182
|
+
client,
|
183
|
+
this.apiClient,
|
184
|
+
mcpserver,
|
185
|
+
configuration,
|
186
|
+
this.openUrl,
|
187
|
+
this.authorized_url
|
188
|
+
);
|
189
|
+
await this.mcpServerManager.addMcpServerWithClient(
|
190
|
+
client,
|
191
|
+
serverName,
|
192
|
+
tools
|
193
|
+
);
|
194
|
+
}
|
195
|
+
|
196
|
+
public getOriginalName(serverName: string): string {
|
197
|
+
return this.serverBriefsMap[serverName].name;
|
198
|
+
}
|
199
|
+
}
|
200
|
+
|
201
|
+
/**
|
202
|
+
* Connect a client to a hosted MCP server session,
|
203
|
+
* prompting for authentication if needed.
|
204
|
+
*/
|
205
|
+
async function connectServer(
|
206
|
+
client: McpClient,
|
207
|
+
apiClient: ApiClient,
|
208
|
+
mcpServer: McpServer,
|
209
|
+
config: McpServerConfiguration | undefined,
|
210
|
+
openUrl: (url: string) => void,
|
211
|
+
authorized_url?: string,
|
212
|
+
noRetry: boolean = false
|
213
|
+
): Promise<void> {
|
214
|
+
const transport = apiClient.mcpSession(mcpServer, config ?? {});
|
215
|
+
|
216
|
+
await client.connect(transport).catch(async (e) => {
|
217
|
+
if (e instanceof AuthenticationRequired && !noRetry) {
|
218
|
+
logger.info("authentication required: " + e.msg);
|
219
|
+
|
220
|
+
const { url, authenticatedP } = await apiClient.authenticate(
|
221
|
+
mcpServer.name,
|
222
|
+
authorized_url
|
223
|
+
);
|
224
|
+
logger.info(`authenticate at url: ${url}`);
|
225
|
+
openUrl(url);
|
226
|
+
const authResult = await authenticatedP;
|
227
|
+
logger.info(`authResult: ${authResult}`);
|
228
|
+
if (!authResult) {
|
229
|
+
throw "authentication failed";
|
230
|
+
}
|
231
|
+
return connectServer(
|
232
|
+
client,
|
233
|
+
apiClient,
|
234
|
+
mcpServer,
|
235
|
+
config,
|
236
|
+
openUrl,
|
237
|
+
authorized_url,
|
238
|
+
true
|
239
|
+
);
|
240
|
+
} else {
|
241
|
+
throw e;
|
242
|
+
}
|
243
|
+
});
|
244
|
+
}
|
245
|
+
|
246
|
+
/**
|
247
|
+
* Given a list of`ServerBrief` objects, create the corresponding list and map
|
248
|
+
* of `SantiizedServerBrief` objects (namely, objects with sanitized names,
|
249
|
+
* holding the original name in a new field).
|
250
|
+
*/
|
251
|
+
function buildServersList(
|
252
|
+
serverList: McpServerBrief[]
|
253
|
+
): [SanitizedServerBrief[], { [serverName: string]: SanitizedServerBrief }] {
|
254
|
+
const servers: SanitizedServerBrief[] = [];
|
255
|
+
const serversMap: { [serverName: string]: SanitizedServerBrief } = {};
|
256
|
+
|
257
|
+
for (const s of serverList) {
|
258
|
+
const ss = SanitizedServerBrief.fromServerBrief(s);
|
259
|
+
servers.push(ss);
|
260
|
+
serversMap[ss.name] = ss;
|
261
|
+
}
|
262
|
+
|
263
|
+
return [servers, serversMap];
|
264
|
+
}
|
265
|
+
|
266
|
+
function sanitizeName(name: string): string {
|
267
|
+
return name.replace("/", "_");
|
268
|
+
}
|
@@ -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/mcpsession/sudomcp/simplecalc",
|
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/mcpsession/sudomcp/simplecalc",
|
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,63 @@
|
|
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(serverBriefs.some((brief) => brief.name === "sudomcp_simplecalc"));
|
57
|
+
}).timeout(20000);
|
58
|
+
}).afterAll(shutdownAll);
|
59
|
+
|
60
|
+
export function prettyPrintTool(tool: Tool): void {
|
61
|
+
console.log(`- ${chalk.green(tool.name)}:`);
|
62
|
+
console.log(` ${chalk.italic(tool.description)}\n`);
|
63
|
+
}
|
package/src/tokenAuth.ts
ADDED
@@ -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,28 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"index": 0,
|
4
|
+
"message": {
|
5
|
+
"role": "assistant",
|
6
|
+
"content": "I'll help you divide those numbers. Let me calculate 12341543 divided by 431234132.",
|
7
|
+
"tool_calls": [
|
8
|
+
{
|
9
|
+
"id": "toolu_017umizET2MjAC2ir5t4iiVR",
|
10
|
+
"type": "function",
|
11
|
+
"function": {
|
12
|
+
"name": "sudomcp_simplecalc__divide",
|
13
|
+
"arguments": "{\"a\": 12341543, \"b\": 431234132}"
|
14
|
+
}
|
15
|
+
}
|
16
|
+
]
|
17
|
+
},
|
18
|
+
"finish_reason": "tool_calls"
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"index": 0,
|
22
|
+
"message": {
|
23
|
+
"role": "assistant",
|
24
|
+
"content": "The result of dividing 12341543 by 431234132 is approximately 0.0286 (or about 1/35)."
|
25
|
+
},
|
26
|
+
"finish_reason": "stop"
|
27
|
+
}
|
28
|
+
]
|
@@ -0,0 +1,4 @@
|
|
1
|
+
{
|
2
|
+
"system_prompt": "Scan a code diff and produce concise and accurate commit messages. The messages should be of the form:\n```\nfeat: added some feature\n```\nOR\n```\nfeat[component]: added some feature\n```\nand ideally be contained on a single line. Instead of `feat`, `fix`, `wip` and `chore` are acceptable tags. Look at file paths and directories for clues regarding which components are being modified. NEVER explain any intermediate steps. Output ONLY the raw message.\n",
|
3
|
+
"mcp_settings": {}
|
4
|
+
}
|
@@ -0,0 +1,5 @@
|
|
1
|
+
Your job is to look at WIP code changes and create a brief but informative description of:
|
2
|
+
- the high level code change being made
|
3
|
+
- the parts that have been completed
|
4
|
+
- suggestions for what should be tackled next
|
5
|
+
I will read the description on Monday morning to refresh my memory. I'll have forgotten some important details, but will still have the broad goals in mind (you can leverage this to keep the description short). I want to get the context back into my mind as quickly as possible.
|
@@ -0,0 +1,4 @@
|
|
1
|
+
{
|
2
|
+
"system_prompt": "Scan a set of git commits and generate a concise pull-request description. If the PR covers multiple concepts, include a bullet list of the form:\n```\n- [ ] Add some feature to some component\n...\n```\nLeave the checkbox empty so the user can fill it in when complete.\nNEVER explain any intermediate steps. Output ONLY the raw message.\n",
|
3
|
+
"mcp_settings": {}
|
4
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
What is 12341543 divided by 431234132?
|
@@ -0,0 +1,4 @@
|
|
1
|
+
{
|
2
|
+
"system_prompt": "Your job is to parse the public README of an mcp server code repository and extract the commands to run the server. You must then create a yaml \"sudomcp\" spec of the form:\n```\nname: mongodb-local\ntitle: MongoDB Local\ndescription: Interact with a local MongoDB instance (via connection string)\nlogo_url: \"https://github.com/sudobase-ai/mcp-servers/blob/master/mongodb-local/logo.png?raw=true\"\nconfig_schema:\n connection_string:\n type: string\n default: \"mongodb://localhost\"\nstdio_server_params:\n command: \"npx\"\n args:\n - \"-y\"\n - \"mongodb-mcp-server\"\n env:\n MDB_MCP_CONNECTION_STRING: $connection_string\n```\nThe above is for a mongodb MCP server, requiring an environment variable `MDB_MCP_CONNECTION_STRING`. The `config_schema` section determines the variables that are required from the user in order to populate the `args` or the `env` values. Each server will have its own configuration requirements and some servers will not require a `config_schema` section at all. The use of \"mcp\" in the name, title and description is redundant and should be avoided.\n\nDo not explain any intermediate steps or give any justification. Just output the yaml file directly so I can pipe it to a file. No ``` or similar notation.\n\n",
|
3
|
+
"mcp_settings": {"duckduckgo-search":{"fetch_content":true}}
|
4
|
+
}
|