Mcp

Deploy MCP Server on Vercel

Copy page

Deploy your own Inkeep MCP server with Next.js on Vercel.

Overview

This self-hosted option gives you full control over your MCP server. The template repo includes two tools that call Inkeep's APIs:

  • ask-question (Use this tool to ask a question about the product)
  • search-knowledge-base (Use this tool to do a semantic search for reference content related to the product)

View the complete template on GitHub →

To deploy on Vercel:

  1. Clone the repository and push to GitHub
  2. Create a new Vercel project and import the repository
  3. Vercel will automatically detect this as a Next.js project. No special build configuration is required.
  4. The MCP server will be deployed at https://your-app.vercel.app/mcp

Authentication

To use the Inkeep MCP server, you’ll need an Inkeep API key.

  • Go to the Inkeep portal and follow these steps Get an API key.
  • When making MCP requests, include your API key in the Authorization header using the Bearer format:
Authorization: Bearer INKEEP_API_KEY

Alternatively, you can specify INKEEP_API_KEY as an environment variable to the MCP server. Be aware: exposing the key this way will make your MCP server accessible to others if not properly secured.

MCP Client Setup

After deploying, configure your MCP client to use your Vercel deployment:

Cursor:

.cursor/mcp.json
{
  "mcpServers": {
    "your-docs-mcp": {
      "url": "https://your-app.vercel.app/mcp",
      "headers": {
        "Authorization": "Bearer <INKEEP_API_KEY>"
      }
    }
  }
}

Claude Desktop:

claude_desktop_config.json
{
  "mcpServers": {
    "your-docs-mcp": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://your-app.vercel.app/mcp"]
    }
  }
}

Implementation Example

Below is a snippet of the server tool that calls the inkeep-rag model:

app/[transport]/route.ts
const ragToolName = 'search-knowledge-base';
const ragToolTitle = 'Search Knowledge Base';
const ragToolDescription = 'Use this tool to do a semantic search for reference content related to the product. The results provided will be extracts from the knowledge base. The content may not fully answer your question -- be circumspect when reviewing and interpreting these extracts before using them in your response.';

server.registerTool(
  ragToolName,
  {
    title: ragToolTitle,
    description: ragToolDescription,
    inputSchema: ragToolInputSchema,
    annotations: {
      readOnlyHint: true,
      openWorldHint: true,
    },
  },
  async ({ query }: { query: string }) => {
    try {
      const response = await openai.chat.completions.parse({
        model: INKEEP_RAG_MODEL,
        messages: [{ role: 'user', content: query }],
        response_format: zodResponseFormat(InkeepRAGResponseSchema, 'InkeepRAGResponseSchema'),
      });

      const parsedResponse = response.choices[0].message.parsed;
      if (parsedResponse) {
        const links =
          parsedResponse.content
            .filter(x => x.url)
            .map(x => `- [${x.title || x.url}](${x.url})`)
            .join('\n') || '';

        await logToInkeepAnalytics({
          apiIntegrationKey: inkeepApiKey,
          properties: {
            tool: ragToolName,
          },
          messagesToLogToAnalytics: [
            { role: 'user', content: query },
            { role: 'assistant', content: links },
          ],
        });

        return {
          content: [
            {
              type: 'text' as const,
              text: JSON.stringify(parsedResponse),
            },
          ],
        };
      }

      return toolError('search-knowledge-base tool returned an empty response.');
    } catch (error) {
      console.error('search-knowledge-base tool failed:', error);
      return toolError(`${ragToolName} failed: ${getErrorMessage(error)}`);
    }
  },
);