Skip to content

Commit bd43384

Browse files
Custom tools via MCP (#153)
**Added a custom MCP server table and MCP view page** This is a (UI) tools run in both chat and can be monitored via a playbook - Allows the user to view mcp servers(only .ts for now) added to the local mcp-source file along with their file name, enable or disable an mcp server, and access a dropdown to view server information and available tools(and their descriptions) for the selected mcp server - The mcp-view page is accessible via the view details button in the dropdown - The mcp-view page allows an mcp server to be deleted from the db and the local files - The mcp-view page allows an mcp tool to be run from the mcp-view page if the server is enabled - The mcp server table has an add mcp server button that brings the user to a page with an upload/drag and drop mechanism to add an mcp server directly to the mcp-source local file. When the server is added, it is automatically built(using the build:mcp-servers script) to be run as a .js file by the Agents local mcp client. - currently mcp servers are run locally using the stdio protocol **Added a tools page(UI)** - allows the user to view all the built in and enabled custom tools and their details, as well as run them. This includes the mcp server name, version, enabled/disabled/not run before(needs to be added to db) status, and Available tools **Local mcp-source file** In the local mcp-source file, users can add custom mcp tools directly to the file. This can also be done via the add mcp servers button page(disabled for now) - users can build a .js file from a .ts mcp server file using the build:mcp-servers, this must be done before a server is available **Test MCP servers** - added simple servers with basic tools to test that the mcp server and its tools are working, eg: (multiplication, add, modulo) tools using two parameters, a testingTool that outputs the input. --------- Signed-off-by: Alexis Rico <[email protected]> Co-authored-by: Alexis Rico <[email protected]>
1 parent ceb2bdb commit bd43384

File tree

29 files changed

+3897
-6
lines changed

29 files changed

+3897
-6
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ tsconfig.tsbuildinfo
2424
*.bun-build
2525

2626
.eval-run-id
27+
28+
dist
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3+
import {
4+
CallToolRequestSchema,
5+
ListResourcesRequestSchema,
6+
ListToolsRequestSchema,
7+
ReadResourceRequestSchema
8+
} from '@modelcontextprotocol/sdk/types.js';
9+
import pg from 'pg';
10+
11+
const server = new Server(
12+
{
13+
name: 'postgres',
14+
version: '0.1.0'
15+
},
16+
{
17+
capabilities: {
18+
resources: {},
19+
tools: {}
20+
}
21+
}
22+
);
23+
24+
const databaseUrl = process.env.DATABASE_URL;
25+
if (!databaseUrl) {
26+
throw new Error('DATABASE_URL environment variable is not set');
27+
}
28+
29+
const resourceBaseUrl = new URL(databaseUrl);
30+
resourceBaseUrl.protocol = 'postgres:';
31+
resourceBaseUrl.password = '';
32+
33+
const pool = new pg.Pool({
34+
connectionString: databaseUrl
35+
});
36+
37+
const SCHEMA_PATH = 'schema';
38+
39+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
40+
const client = await pool.connect();
41+
try {
42+
const result = await client.query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'");
43+
return {
44+
resources: result.rows.map((row) => ({
45+
uri: new URL(`${row.table_name}/${SCHEMA_PATH}`, resourceBaseUrl).href,
46+
mimeType: 'application/json',
47+
name: `"${row.table_name}" database schema`
48+
}))
49+
};
50+
} finally {
51+
client.release();
52+
}
53+
});
54+
55+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
56+
const resourceUrl = new URL(request.params.uri);
57+
58+
const pathComponents = resourceUrl.pathname.split('/');
59+
const schema = pathComponents.pop();
60+
const tableName = pathComponents.pop();
61+
62+
if (schema !== SCHEMA_PATH) {
63+
throw new Error('Invalid resource URI');
64+
}
65+
66+
const client = await pool.connect();
67+
try {
68+
const result = await client.query(
69+
'SELECT column_name, data_type FROM information_schema.columns WHERE table_name = $1',
70+
[tableName]
71+
);
72+
73+
return {
74+
contents: [
75+
{
76+
uri: request.params.uri,
77+
mimeType: 'application/json',
78+
text: JSON.stringify(result.rows, null, 2)
79+
}
80+
]
81+
};
82+
} finally {
83+
client.release();
84+
}
85+
});
86+
87+
server.setRequestHandler(ListToolsRequestSchema, async () => {
88+
return {
89+
tools: [
90+
{
91+
name: 'query',
92+
description: 'Run a read-only SQL query',
93+
inputSchema: {
94+
type: 'object',
95+
properties: {
96+
sql: { type: 'string' }
97+
}
98+
}
99+
}
100+
]
101+
};
102+
});
103+
104+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
105+
if (request.params.name === 'query') {
106+
const sql = request.params.arguments?.sql as string;
107+
108+
const client = await pool.connect();
109+
try {
110+
await client.query('BEGIN TRANSACTION READ ONLY');
111+
const result = await client.query(sql);
112+
return {
113+
content: [{ type: 'text', text: JSON.stringify(result.rows, null, 2) }],
114+
isError: false
115+
};
116+
} catch (error) {
117+
throw error;
118+
} finally {
119+
client.query('ROLLBACK').catch((error) => console.warn('Could not roll back transaction:', error));
120+
121+
client.release();
122+
}
123+
}
124+
throw new Error(`Unknown tool: ${request.params.name}`);
125+
});
126+
127+
async function runServer() {
128+
const transport = new StdioServerTransport();
129+
await server.connect(transport);
130+
}
131+
132+
runServer().catch(console.error);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"module": "ES2022",
5+
"moduleResolution": "node",
6+
"esModuleInterop": true,
7+
"strict": true,
8+
"skipLibCheck": true,
9+
"outDir": "./dist"
10+
},
11+
"include": ["./*.ts"]
12+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CREATE TABLE "mcp_servers" (
2+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3+
"name" text NOT NULL,
4+
"server_name" text NOT NULL,
5+
"file_path" text NOT NULL,
6+
"version" text NOT NULL,
7+
"enabled" boolean DEFAULT true NOT NULL,
8+
"env_vars" jsonb DEFAULT '{}' :: jsonb NOT NULL,
9+
"created_at" timestamp DEFAULT now() NOT NULL,
10+
CONSTRAINT "uq_mcp_servers_name" UNIQUE("name"),
11+
CONSTRAINT "uq_mcp_servers_server_name" UNIQUE("server_name")
12+
);
13+
14+
--> statement-breakpoint
15+
ALTER TABLE
16+
"mcp_servers" ENABLE ROW LEVEL SECURITY;
17+
18+
--> statement-breakpoint
19+
CREATE POLICY "mcp_servers_policy" ON "mcp_servers" AS PERMISSIVE FOR ALL TO "authenticated_user" USING (true);

0 commit comments

Comments
 (0)