@sentio/mcp
Version:
sentio mcp
176 lines (154 loc) • 6.96 kB
text/typescript
import { Client, createClient } from "@hey-api/client-fetch";
import { FastMCP } from "fastmcp";
import { z } from "zod";
import { registerWebTools } from "./tools/web_tools.js";
import { registerDataTools } from "./tools/data_tools.js";
import { registerPriceTools } from "./tools/price_tools.js";
import { registerAlertsTools } from "./tools/alerts_tools.js";
import { registerProcessorTools } from "./tools/processor_tools.js";
import { registerDebugSimulationTools } from "./tools/debug_simulation_tools.js";
// Create an adapter object to bridge between FastMCP and McpServer interfaces
class ServerAdapter<T extends Record<string, unknown> | undefined> {
private server: FastMCP<T>;
private client: Client;
constructor(server: FastMCP<T>, client: Client) {
this.server = server;
this.client = client;
}
// Implement the `tool` method used by the tools
tool(name: string, description: string, parameters: any, execute: any) {
this.server.addTool({
name: name,
description: description,
parameters: z.object(parameters),
execute: async (args: any, { session }) => {
console.log(`[serve] Executing tool: ${name}, session:`, {
hasToken: !!session?.token,
hasApiKey: !!session?.apiKey
});
this.client.interceptors.request.use((request) => {
if (session?.token) {
request.headers.set("Authorization", `${session.token}`);
console.log(`[serve] Set Authorization from session for tool: ${name}`);
} else if (session?.apiKey) {
request.headers.set("api-key", `${session.apiKey}`);
console.log(`[serve] Set api-key from session for tool: ${name}`);
}
return request;
})
// Pass the context to tools so they can access auth
try {
const result = await execute(args);
console.log(`[serve] Tool ${name} executed successfully`);
return result;
} catch (error) {
console.error(`[serve] Tool ${name} execution failed:`, error);
throw error;
}
}
});
}
}
function decodeJWT(token: string) {
const parts = token.split('.');
const json = Buffer.from(parts[1], 'base64').toString();
return JSON.parse(json);
}
export async function runServe(options: any) {
const port = options.port || 3000;
console.log("[serve] Starting server with options:", {
port,
hasApiKey: !!options.apiKey,
hasToken: !!options.token,
host: options.host || "https://app.sentio.xyz"
});
// Create FastMCP server instance
const server = new FastMCP({
name: "Sentio MCP Server",
version: "1.1.5",
authenticate: async (request) => {
console.log("[serve] Authenticating request, headers:", {
hasAuth: !!request.headers["authorization"],
hasApiKey: !!request.headers["api-key"],
hasOptionsApiKey: !!options.apiKey
});
// Extract auth from headers
const authHeader = request.headers["authorization"];
const apiKeyHeader = request.headers["api-key"];
if (authHeader) {
// JWT authentication
console.log("[serve] Using JWT authentication");
const { sub } = decodeJWT(authHeader);
return { sub, token: authHeader.startsWith('Bearer ') ? authHeader : `Bearer ${authHeader}` };
} else if (apiKeyHeader) {
// API Key authentication
console.log("[serve] Using API Key from header");
return { sub: apiKeyHeader, apiKey: apiKeyHeader };
} else if (options.apiKey) {
// Fallback to options.apiKey
console.log("[serve] Using API Key from options");
return { sub: options.apiKey, apiKey: options.apiKey };
}
console.error("[serve] Authentication failed - no credentials provided");
throw new Response(null, {
status: 401,
statusText: "Unauthorized"
});
}
});
// Create API client for tools
const client = createClient({
baseUrl: options.host || "https://app.sentio.xyz",
});
console.log("[serve] Created API client with baseUrl:", options.host || "https://app.sentio.xyz");
// We'll set up the auth interceptor using the context
client.interceptors.request.use((request, context: any) => {
console.log("[serve] Request interceptor - setting auth headers");
if (options.apiKey) {
request.headers.set("api-key", options.apiKey);
console.log("[serve] Set api-key header from options");
} else if (options.token) {
request.headers.set("Authorization", `Bearer ${options.token}`);
console.log("[serve] Set Authorization header from options");
}
return request;
});
// Create an adapter to make the tools work with FastMCP
const adapter = new ServerAdapter(server, client);
console.log("[serve] Registering tools...");
// Register all tools from tools directory using the adapter
registerWebTools(adapter as any, client, options);
registerDataTools(adapter as any, client, options);
registerPriceTools(adapter as any, client, options);
registerAlertsTools(adapter as any, client, options);
registerProcessorTools(adapter as any, client, options);
registerDebugSimulationTools(adapter as any, client, options);
console.log("[serve] All tools registered");
// Track connections and disconnections
server.on("connect", (event) => {
const session = event.session;
console.log("[serve] Client connected, session:", {
hasToken: !!(session as any).token,
hasApiKey: !!(session as any).apiKey,
roots: session.roots
});
// Access the current roots
console.log("Initial roots:", session.roots);
// Listen for changes to the roots
session.on("rootsChanged", (event) => {
console.log("Roots changed:", event.roots);
});
});
server.on("disconnect", (event) => {
console.log(`[serve] Client disconnected: ${event.session}`);
});
// Start the server with HTTP transport
await server.start({
transportType: "sse",
sse: {
endpoint: "/sse",
port,
}
});
console.error(`MCP Server running at http://localhost:${port}`);
}