Browse documentation
Guides

Drishti MCP for Chat

This guide covers the server-side core of using Drishti MCP inside a chat backend: connect to the hosted MCP server, expose tools to your model, run the agent loop, and stream a response. It does not cover UI components — only MCP connection, tool bridging, agent execution, and streaming.

The pattern is provider-agnostic. Drishti MCP speaks standard streamable HTTP; your agent runtime (OpenAI, Anthropic, OpenRouter, Groq, Google, or an agent framework) handles model calls and tool selection.

Mental model

Every chat integration follows the same loop, regardless of provider:

  1. Connect to Drishti MCP over streamable HTTP.
  2. List tools — Drishti returns names, descriptions, and input schemas.
  3. Register tools with your agent runtime in the format that runtime expects.
  4. Run the agent loop — the model may request tool calls; your backend executes callTool on Drishti and returns results.
  5. Stream or return the final answer to the client.
  6. Close the MCP session when the request ends.
text
User message
    ↓
Chat API route
    ↓
Orchestrator
    ↓
LLM (any provider)  ──calls──►  Drishti MCP tools  (get_top_movers, get_news, …)
    ↓
Streamed response  ──►  client

You do not hand-write JSON schemas for each Drishti tool. MCP listTools supplies them; your job is to bridge those definitions into your runtime and route callTool back to Drishti.

Choose an agent runtime

Pick the integration path that matches your stack. All paths use the same Drishti endpoint and Bearer auth.

RuntimeBest whenMCP wiring
Vercel AI SDK (ai)You want one tool-loop API across OpenAI, Anthropic, OpenRouter, Groq, Google, and othersBridge listTools → tool() definitions; execute with callTool
OpenAI Agents SDKYou want native mcpServers on an Agent and a built-in run loopMCPServerStreamableHttp + connectMcpServers
Provider SDK directlyYou already use OpenAI, Anthropic, or OpenRouter HTTP APIs without a frameworkMap listTools to that provider's tool/function schema

The sections below show the portable MCP client first, then two common runtime patterns. Swap the model provider without changing the Drishti connection.

Prerequisites

  • A Drishti API key from the platform console.
  • Node.js 18+ for the TypeScript examples.
  • An API key for your LLM provider (OpenAI, Anthropic, OpenRouter, Groq, Google, etc.).
  • A chat API route or backend handler where you can open one MCP session per request.

Environment variables

bash
# Drishti MCP
DRISHTI_API_KEY=your_key_here
DRISHTI_MCP_URL=https://mcp.drishti.manasija.in

# LLM provider (use the keys your runtime needs)
OPENAI_API_KEY=sk-...
# ANTHROPIC_API_KEY=...
# OPENROUTER_API_KEY=...
# GROQ_API_KEY=...

Drishti MCP uses streamable HTTP transport. Send your Drishti API key as a Bearer token on each MCP request.

Connect to Drishti MCP

Use the official MCP TypeScript SDK for a provider-neutral client. Agent frameworks may wrap this internally, but the contract is the same: connect → listTools → callTool → close.

pnpm add @modelcontextprotocol/sdk
ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"

const DRISHTI_MCP_URL = process.env.DRISHTI_MCP_URL ?? "https://mcp.drishti.manasija.in"

export async function connectDrishtiMcp(apiKey: string) {
  const transport = new StreamableHTTPClientTransport(
    new URL(DRISHTI_MCP_URL),
    {
      requestInit: {
        headers: { Authorization: `Bearer ${apiKey}` },
      },
    },
  )

  const client = new Client({ name: "drishti-chat", version: "1.0.0" })
  await client.connect(transport)
  return client
}

List tools once per request (or cache per process if your traffic pattern allows):

ts
const BLOCKED = new Set(["search_docs", "get_doc"])

export async function listMarketTools(client: Client) {
  const { tools } = await client.listTools()
  return tools.filter((tool) => tool.name && !BLOCKED.has(tool.name))
}

For a market-research chat agent, filter out documentation tools (search_docs, get_doc) so the model stays on live data tools such as get_top_movers, get_news, and get_announcements.

Execute a tool when the model requests it:

ts
const result = await client.callTool({
  name: "get_top_movers",
  arguments: { exchange: "NSE" },
})

Always call client.close() when the chat request finishes.

Pattern A: Vercel AI SDK (any provider)

The Vercel AI SDK is a practical default when you want one tool-loop API and the freedom to swap models through provider packages.

Install the core packages plus one or more provider packages:

pnpm add ai zod @ai-sdk/openai @ai-sdk/anthropic @openrouter/ai-sdk-provider

Bridge MCP tools to AI SDK

Convert each MCP tool into an AI SDK tool() whose execute handler calls Drishti:

ts
import { tool, streamText, stepCountIs } from "ai"
import { z } from "zod"
import type { Client } from "@modelcontextprotocol/sdk/client/index.js"

function mcpToolsToAiSdk(mcp: Client, mcpTools: { name: string; description?: string }[]) {
  return Object.fromEntries(
    mcpTools.map((entry) => [
      entry.name,
      tool({
        description: entry.description ?? entry.name,
        // Map MCP inputSchema to zod, or use jsonSchema() from the AI SDK
        inputSchema: z.object({}).passthrough(),
        execute: async (args) =>
          mcp.callTool({ name: entry.name, arguments: args }),
      }),
    ]),
  )
}

In production, derive each tool's inputSchema from the MCP inputSchema field instead of passthrough().

Swap the model provider

Only the model import changes. The Drishti MCP client and tool bridge stay the same.

ts
// OpenAI
import { createOpenAI } from "@ai-sdk/openai"
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! })
const model = openai("gpt-4.1")

// Anthropic
import { createAnthropic } from "@ai-sdk/anthropic"
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! })
const model = anthropic("claude-sonnet-4-20250514")

// OpenRouter (route to any supported model)
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY! })
const model = openrouter("anthropic/claude-sonnet-4")

Run the chat loop

ts
export async function runDrishtiChat(
  userMessage: string,
  options?: { signal?: AbortSignal },
) {
  const mcp = await connectDrishtiMcp(process.env.DRISHTI_API_KEY!)

  try {
    const mcpTools = await listMarketTools(mcp)
    const tools = mcpToolsToAiSdk(mcp, mcpTools)

    const result = streamText({
      model, // from OpenAI, Anthropic, OpenRouter, Groq, etc.
      system: [
        "You are an Indian stock market research assistant (NSE/BSE).",
        "Use Drishti tools for live prices, movers, news, earnings, and announcements.",
        "Cite which tool produced each fact.",
      ].join(" "),
      prompt: userMessage,
      tools,
      stopWhen: stepCountIs(8),
      abortSignal: options?.signal,
    })

    return {
      response: result.toUIMessageStreamResponse(),
      cleanup: () => mcp.close(),
    }
  } catch (error) {
    await mcp.close()
    throw error
  }
}

streamText runs the tool loop: the model requests a tool, your execute handler calls Drishti, results go back to the model until it produces a final answer. Return toUIMessageStreamResponse() for AI SDK chat UIs, or toTextStreamResponse() for plain text.

Pattern B: OpenAI Agents SDK (native MCP)

If you use the OpenAI Agents SDK, you can attach Drishti as mcpServers and skip manual tool bridging. The SDK calls listTools and callTool for you.

pnpm add @openai/agents @openai/agents-extensions ai @ai-sdk/openai @openrouter/ai-sdk-provider
ts
import {
  Agent,
  run,
  MCPServerStreamableHttp,
  connectMcpServers,
} from "@openai/agents"
import { createOpenRouter } from "@openrouter/ai-sdk-provider"
import { aisdk } from "@openai/agents-extensions/ai-sdk"

const drishti = new MCPServerStreamableHttp({
  url: process.env.DRISHTI_MCP_URL ?? "https://mcp.drishti.manasija.in",
  name: "Drishti",
  cacheToolsList: true,
  requestInit: {
    headers: { Authorization: `Bearer ${process.env.DRISHTI_API_KEY}` },
  },
})

const mcp = await connectMcpServers([drishti])

// Bridge any AI SDK provider — OpenAI, Anthropic, OpenRouter, Groq, etc.
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY! })
const model = aisdk(openrouter("anthropic/claude-sonnet-4"))

const agent = new Agent({
  name: "Market Agent",
  instructions: "Answer using Drishti MCP tools for Indian market data.",
  model: model as never,
  mcpServers: mcp.active as never[],
})

try {
  const result = await run(agent, "What are today's top movers on NSE?", {
    stream: true,
  })
  // use createAiSdkUiMessageStreamResponse(result) to stream to the client
} finally {
  await mcp.close()
}

aisdk() is the bridge: your agent runtime stays the OpenAI Agents SDK, but the model can come from any Vercel AI SDK provider.

Pattern C: Provider API directly

If you call OpenAI, Anthropic, or OpenRouter HTTP APIs without a framework, the loop is the same:

  1. listTools from Drishti MCP.
  2. Map each tool to the provider's tool or function schema.
  3. Send the user message plus tools to the chat completion endpoint.
  4. When the model returns tool calls, run callTool on Drishti and append results to the message history.
  5. Repeat until the model returns a final text response.

OpenRouter exposes an OpenAI-compatible chat API, so one client can route to Claude, GPT, Gemini, Llama, and other models with the same tool payload shape.

Lifecycle rules

These apply regardless of runtime:

  1. Open one MCP session per chat request (or per conversation if you pool sessions yourself).
  2. Always `close()` the MCP client in finally or after the stream ends.
  3. Pass `AbortSignal` through so client disconnects cancel in-flight tool calls.
  4. Tell the model to use tools in system instructions — models skip tools they are not prompted to consider.

Chat API route

Pipe the stream and ensure MCP cleanup when the response completes:

ts
import { runDrishtiChat } from "@/lib/run-chat"

export async function POST(req: Request) {
  const { message } = await req.json()

  if (!message?.trim()) {
    return Response.json({ error: "message required" }, { status: 400 })
  }

  const { response, cleanup } = await runDrishtiChat(message, {
    signal: req.signal,
  })

  const body = response.body
  if (!body) {
    await cleanup()
    return response
  }

  const reader = body.getReader()
  const stream = new ReadableStream({
    async start(controller) {
      try {
        while (true) {
          const { done, value } = await reader.read()
          if (done) break
          controller.enqueue(value)
        }
      } finally {
        await cleanup()
        controller.close()
      }
    },
    cancel() {
      void cleanup()
    },
  })

  return new Response(stream, {
    status: response.status,
    headers: response.headers,
  })
}

The route shape is the same for Pattern A and Pattern B — only the orchestrator behind runDrishtiChat changes.

Available tools

After connect, the model can call tools such as get_top_movers, get_symbols, get_announcements, get_news, list_earnings, and search_concalls — whatever Drishti returns from listTools. See the Drishti MCP tool catalog for the full list.

Suggested project layout

ConcernTypical location
MCP connect and teardownmcp/drishti.ts
Tool list and filteringmcp/tools.ts
Runtime bridge (AI SDK, Agents SDK, etc.)agents/ or lib/agent.ts
Model provider configproviders/index.ts
Chat orchestrationlib/run-chat.ts
HTTP entryapp/api/chat/route.ts (or your framework equivalent)

The chat-drishti-mcp demo on GitHub follows this layout. Try the live version or use the repo as a starting point.

Split files the way your codebase already organizes routes and shared libraries. The important boundary is one MCP connect per request and guaranteed `close()`.

Debugging

SymptomLikely cause
Model never calls toolsTools not passed to the runtime, or system prompt does not mention using them
401 from MCPMissing or invalid DRISHTI_API_KEY
Tools list emptyWrong MCP URL, server unavailable, or tools filtered out
Hangs after responseMCP close() not called
Tool schema errorsMCP inputSchema not mapped correctly to your runtime's tool format
Unexpected tool names in logsSome runtimes prefix tool names with the MCP server name

Inspect available tools after connect:

ts
const { tools } = await client.listTools()
console.log(tools.map((t) => t.name))

MCP vs SDK in production

Use Drishti MCP when the model should choose tools conversationally inside a chat or agent runtime.

Use the JavaScript / TypeScript SDK, Python SDK, or WebSocket streams when you need deterministic fetches in cron jobs, dashboards, alert pipelines, or other code paths that should not depend on LLM tool selection.

MCP tool calls consume Drishti credits on data tools. See Pricing for plan behavior.