@sentio/mcp
Version:
sentio mcp
218 lines (206 loc) • 6.8 kB
text/typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { DataService, common, WebService } from "@sentio/api";
import z from "zod";
import { Client } from "@hey-api/client-fetch";
import { getProjectId } from "./web_tools.js";
export function registerDataTools(server: McpServer, client: Client, options: any) {
server.tool("executeSql", "Execute SQL in a project", {
owner: z.string().describe("Project owner"),
slug: z.string().describe("Project slug"),
query: z.string().describe("SQL query to execute"),
version: z.number().default(0).describe("Version of the project"),
cursor: z.string().optional().describe("Cursor to paginate results"),
size: z.number().default(100).describe("Number of results to return"),
parameters: z.record(z.string(), z.any()).optional().describe("Parameters to pass to the query")
}, async ({ owner, slug, query, version, cursor, size, parameters }) => {
const response = await DataService.executeSql({
path: {
owner,
slug
},
body: {
sqlQuery: {
sql: query,
size,
parameters: toRichStruct(parameters)
},
version,
cursor,
},
client
});
if (response.error) {
throw response.error;
}
return {
content: [{
type: "text",
text: JSON.stringify(response.data),
}]
};
});
server.tool("queryEventLog", "Query event logs", {
owner: z.string().describe("Project owner"),
slug: z.string().describe("Project slug"),
query: z.object({
from: z.string().optional(),
to: z.string().optional(),
limit: z.number().optional(),
offset: z.number().optional(),
filters: z.array(z.object({
field: z.string(),
operator: z.string(),
value: z.string()
})).optional()
})
}, async ({ owner, slug, query }) => {
const response = await DataService.queryLog({
path: {
owner,
slug
},
body: query,
client
});
if (response.error) {
throw response.error;
}
return {
content: [{
type: "text",
text: JSON.stringify(response.data),
}]
};
});
server.tool("getMetrics", "Get a list of metrics in a project", {
owner: z.string().describe("Project owner"),
slug: z.string().describe("Project slug"),
version: z.number().describe("Version of the project").default(0)
}, async ({ owner, slug, version }) => {
const projectId = await getProjectId(client, owner, slug)
const response = await DataService.getMetrics({
query: {
projectId: projectId,
version: version
},
client
});
if (response.error) {
throw response.error;
}
return {
content: [{
type: "text",
text: JSON.stringify(response.data),
}]
};
});
server.tool("queryRange", "Query metrics range", {
owner: z.string().describe("Project owner"),
slug: z.string().describe("Project slug"),
timeRange: z.object({
start: z.string().default("now-30d").describe("Start time"),
end: z.string().default("now").describe("End time"),
step: z.number().describe("Step"),
timezone: z.string().default("UTC").optional().describe("Timezone")
}),
queries: z.array(z.object({
query: z.string().optional(),
alias: z.string().optional(),
id: z.string().optional(),
labelSelector: z.record(z.string()).optional(),
aggregate: z.any().optional(),
functions: z.array(z.object({
name: z.string(),
parameters: z.record(z.any()).optional()
})).optional(),
color: z.string().optional(),
disabled: z.boolean().optional()
})).describe("Array of metric queries"),
version: z.number().optional().describe("Version of the project").default(0)
}, async ({ owner, slug, queries, timeRange, version }) => {
const response = await DataService.queryRange({
path: {
owner,
slug
},
body: {
timeRange,
queries,
version: version ?? 0
},
client
})
if (response.error) {
throw response.error;
}
return {
content: [{
type: "text",
text: JSON.stringify(response.data),
}]
}
})
}
function toRichStruct(parameters?: Record<string, any>): common.RichStruct | undefined {
if (!parameters) {
return undefined;
}
return {
fields: Object.fromEntries(Object.entries(parameters).map(([key, value]) => [key, toRichValue(value)]))
}
}
function toRichValue(value: any): common.RichValue {
if (value === null) {
return {
nullValue: "NULL_VALUE"
}
}
switch (typeof value) {
case "number":
if (Number.isInteger(value)) {
return {
intValue: value
}
}
return {
floatValue: value
}
case "bigint":
return {
bigintValue: toBigInteger(value)
}
case "string":
return {
stringValue: value
}
case "boolean":
return {
boolValue: value
}
case "object":
if (value instanceof Date) {
return {
timestampValue: value.toISOString()
}
}
if (Array.isArray(value)) {
return {
listValue: {
values: value.map(v => toRichValue(v))
}
}
}
return {
structValue: toRichStruct(value)
}
default:
throw new Error(`Unsupported value type: ${typeof value}`);
}
}
function toBigInteger(value: bigint): common.BigInteger {
return {
negative: value < 0n,
data: value.toString(16)
}
}