Skip to main content

MCP Tools API Reference

This page documents the MCP (Model Context Protocol) JSON-RPC API for programmatic integration.

For End Users

If you're setting up ChatGPT or Claude, see the MCP Connectors Guide instead.


Overview

The MCP server implements the Streamable HTTP transport and exposes the synjar_search tool for searching your Synjar knowledge base via JSON-RPC 2.0 protocol.

Base URL: https://api.synjar.com/mcp/{token}

Protocol: JSON-RPC 2.0 / MCP Streamable HTTP Methods: POST (JSON-RPC), OPTIONS (CORS), GET (returns 405) Content-Type: application/json

Supported JSON-RPC Methods:

MethodDescription
initializeReturns server capabilities and protocol version
tools/listReturns available tools and their schemas
tools/callExecutes the synjar_search tool

Typical MCP Session Flow

Client                          Server
| |
|--initialize------------------>|
|<--capabilities----------------|
| |
|--tools/list------------------>|
|<--[synjar_search]-------------|
| |
|--tools/call (search)--------->|
|<--results---------------------|
| |
|--tools/call (search)--------->|
|<--results---------------------|

The typical session starts with initialize to negotiate capabilities, then tools/list to discover available tools, followed by one or more tools/call requests to perform searches.


Authentication

Authentication is done via the token in the URL path:

POST https://api.synjar.com/mcp/{PUBLIC_TOKEN}

The {PUBLIC_TOKEN} is your Search Link's public token (64-character hex string).

Security
  • Tokens are sensitive—treat them like API keys
  • Use HTTPS only (TLS 1.3)
  • Store tokens securely (environment variables, secrets manager)

Method: initialize

Returns server capabilities and protocol version. This is typically the first method called by MCP clients.

Request Format

{
"jsonrpc": "2.0",
"id": "init-1",
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "ChatGPT",
"version": "1.0"
}
}
}

Response Format

{
"jsonrpc": "2.0",
"id": "init-1",
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {}
},
"serverInfo": {
"name": "Synjar MCP Server",
"version": "1.0.0"
}
}
}

Method: tools/list

Returns available tools and their JSON Schema definitions. Use this to discover what tools are available.

Request Format

{
"jsonrpc": "2.0",
"id": "list-1",
"method": "tools/list",
"params": {}
}

Response Format

{
"jsonrpc": "2.0",
"id": "list-1",
"result": {
"tools": [
{
"name": "synjar_search",
"description": "Search the Synjar knowledge base. Available tags: docs, faq",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Natural language search query (2-256 characters)"
},
"limit": {
"type": "number",
"description": "Maximum results to return (default: 5, max: 20)"
},
"tags": {
"type": "array",
"items": { "type": "string" },
"description": "Filter by document tags. Allowed: docs, faq"
}
},
"required": ["query"]
}
}
]
}
}

The description field dynamically includes allowed tags from your Search Link configuration.


Search a Synjar knowledge base for relevant documents and chunks.

Request Format

{
"jsonrpc": "2.0",
"id": "unique-request-id",
"method": "tools/call",
"params": {
"name": "synjar_search",
"arguments": {
"query": "your search query",
"limit": 5,
"tags": ["optional", "filter", "tags"]
}
}
}

Parameters

ParameterTypeRequiredDescription
querystringYesNatural language search query (2-256 characters)
limitnumberNoMax results to return (default: 5, max: 20)
tagsstring[]NoFilter by document tags (must be subset of allowed tags)

Response Format

{
"jsonrpc": "2.0",
"id": "unique-request-id",
"result": {
"content": [
{
"type": "text",
"text": "{\"results\":[{\"title\":\"...\",\"content\":\"...\",\"score\":0.95,\"sourceUrl\":\"...\"}],\"totalCount\":3}"
}
]
}
}

The text field contains a JSON-encoded object with:

FieldTypeDescription
resultsarrayArray of search results
results[].titlestringDocument title
results[].contentstringRelevant chunk content
results[].scorenumberRelevance score (0-1, higher is better)
results[].sourceUrlstringURL to source document (if available)
totalCountnumberTotal number of results returned

Error Codes

MCP uses standard JSON-RPC error codes:

HTTPJSON-RPC CodeErrorDescription
400-32700Parse errorInvalid JSON
400-32600Invalid requestInvalid JSON-RPC structure
400-32602Invalid paramsParameter validation failed
403-32002ForbiddenInvalid, inactive, or expired token
429-32000Rate limitRate limit exceeded
500-32603Internal errorServer error

Error Response Format

{
"jsonrpc": "2.0",
"id": "unique-request-id",
"error": {
"code": -32602,
"message": "Query must be 2-256 characters"
}
}

Examples

Request:

curl -X POST https://api.synjar.com/mcp/abc123def456... \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "req-1",
"method": "tools/call",
"params": {
"name": "synjar_search",
"arguments": {
"query": "refund policy"
}
}
}'

Response:

{
"jsonrpc": "2.0",
"id": "req-1",
"result": {
"content": [
{
"type": "text",
"text": "{\"results\":[{\"title\":\"Refund Policy\",\"content\":\"Full refund within 30 days of purchase...\",\"score\":0.95,\"sourceUrl\":\"https://app.synjar.com/docs/123\"}],\"totalCount\":1}"
}
]
}
}

Example 2: Search with Tags Filter

Request:

{
"jsonrpc": "2.0",
"id": "req-2",
"method": "tools/call",
"params": {
"name": "synjar_search",
"arguments": {
"query": "installation steps",
"limit": 10,
"tags": ["documentation", "setup"]
}
}
}

Response:

{
"jsonrpc": "2.0",
"id": "req-2",
"result": {
"content": [
{
"type": "text",
"text": "{\"results\":[{\"title\":\"Getting Started Guide\",\"content\":\"Follow these installation steps to set up Synjar...\",\"score\":0.92,\"sourceUrl\":\"https://app.synjar.com/docs/456\"},{\"title\":\"Docker Installation\",\"content\":\"Run docker-compose up to start the application...\",\"score\":0.87,\"sourceUrl\":\"https://app.synjar.com/docs/789\"}],\"totalCount\":2}"
}
]
}
}

Note: Only documents with matching tags (documentation OR setup) are returned. The tags filter is additive (OR logic), and all requested tags must be in the Search Link's allowedTags list.

Example 3: Invalid Token Error

Request:

curl -X POST https://api.synjar.com/mcp/invalid-token \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"req-3","method":"tools/call","params":{"name":"synjar_search","arguments":{"query":"test"}}}'

Response:

{
"jsonrpc": "2.0",
"id": "req-3",
"error": {
"code": -32002,
"message": "Invalid or expired token"
}
}

Example 4: Rate Limit Error

Response:

{
"jsonrpc": "2.0",
"id": "req-4",
"error": {
"code": -32000,
"message": "Rate limit exceeded (30 requests/minute)",
"data": {
"retryAfter": 45
}
}
}

Rate Limits

Two-tier rate limiting:

  1. Per IP: 100 requests/minute (prevents enumeration attacks)
  2. Per PublicLink: 30 requests/minute (prevents abuse of specific links)

Rate limit headers:

X-RateLimit-Limit: 30              # Max requests per window
X-RateLimit-Remaining: 25 # Requests left in current window
X-RateLimit-Reset: 1704067200 # Unix timestamp when window resets
Retry-After: 45 # Seconds to wait (only on 429)

Client retry logic:

if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return retry(request);
}

Client Libraries

JavaScript/TypeScript

async function searchSynjar(
token: string,
query: string,
limit = 5
): Promise<any> {
const response = await fetch(`https://api.synjar.com/mcp/${token}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: Math.random().toString(),
method: 'tools/call',
params: {
name: 'synjar_search',
arguments: { query, limit },
},
}),
});

const data = await response.json();

if (data.error) {
throw new Error(data.error.message);
}

return JSON.parse(data.result.content[0].text);
}

// Usage
const results = await searchSynjar(
'abc123def456...',
'refund policy',
10
);
console.log(results.results);

Python

import requests
import json

def search_synjar(token: str, query: str, limit: int = 5) -> dict:
url = f"https://api.synjar.com/mcp/{token}"

payload = {
"jsonrpc": "2.0",
"id": "req-1",
"method": "tools/call",
"params": {
"name": "synjar_search",
"arguments": {
"query": query,
"limit": limit
}
}
}

response = requests.post(url, json=payload)
data = response.json()

if "error" in data:
raise Exception(data["error"]["message"])

return json.loads(data["result"]["content"][0]["text"])

# Usage
results = search_synjar("abc123def456...", "refund policy", 10)
for result in results["results"]:
print(f"{result['title']}: {result['score']}")

Error Handling

Common Errors

CodeNameDescription
-32600Invalid RequestMalformed JSON-RPC request
-32601Method Not FoundUnknown method name
-32602Invalid ParamsInvalid method parameters
-32603Internal ErrorServer-side error
429Rate LimitedToo many requests (100/min per token)

TypeScript Error Handling Example

async function searchWithRetry(token: string, query: string, retries = 3) {
for (let i = 0; i < retries; i++) {
const response = await fetch(`https://api.synjar.com/mcp/${token}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: { name: 'synjar_search', arguments: { query } }
})
});

if (response.status === 429) {
// Rate limited - wait and retry
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
continue;
}

const json = await response.json();
if (json.error) {
throw new Error(`MCP Error ${json.error.code}: ${json.error.message}`);
}
return json.result;
}
throw new Error('Max retries exceeded');
}

Python Error Handling Example

import requests
import time

def search_with_retry(token: str, query: str, retries: int = 3) -> dict:
url = f"https://api.synjar.com/mcp/{token}"
payload = {
"jsonrpc": "2.0",
"id": "req-1",
"method": "tools/call",
"params": {
"name": "synjar_search",
"arguments": {"query": query}
}
}

for i in range(retries):
response = requests.post(url, json=payload)

if response.status_code == 429:
# Rate limited - wait and retry with exponential backoff
time.sleep((i + 1) * 1)
continue

data = response.json()
if "error" in data:
raise Exception(f"MCP Error {data['error']['code']}: {data['error']['message']}")

return data["result"]

raise Exception("Max retries exceeded")

Privacy & Security

Query Text Storage

By default, query text is NOT stored. Only aggregate counts are tracked.

When historyMode = OFF (default):

  • ✅ Request count tracked
  • ✅ Latency metrics collected
  • ❌ Query text NOT stored

When historyMode = ON:

  • ✅ Request count tracked
  • ✅ Latency metrics collected
  • ✅ Query text stored for 90 days (auto-deleted after)

IP & User-Agent Hashing

IP addresses and User-Agent strings are hashed (SHA-256) for privacy:

  • One-way hashing (cannot be reversed)
  • Used only for abuse detection
  • NOT shared with third parties

Privacy Compliance

  • GDPR Article 17 (Right to Erasure): Use workspace anonymization workflow to delete all personal data
  • CCPA compliance: Query text is considered personal data when stored (requires disclosure)
  • 90-day retention: Query text is automatically deleted after 90 days
  • Full details: Privacy Policy

Log Scrubbing

MCP tokens and query text are scrubbed from access logs:

GET /mcp/abc123... → GET /mcp/[REDACTED]

Troubleshooting

Invalid JSON-RPC Request

Error: -32600 Invalid request

Common causes:

  • Missing jsonrpc: "2.0" field
  • Wrong method (use tools/call)
  • Missing params object

Fix: Ensure your request matches the Request Format exactly.

Invalid Parameters

Error: -32602 Invalid params

Common causes:

  • Query too short (< 2 chars) or too long (> 256 chars)
  • Limit out of range (must be 1-20)
  • Tags not in allowed tags list

Fix: Validate parameters before sending request.

Connection Timeout

Problem: Request times out after 30 seconds.

Causes:

  • Large dataset (millions of chunks)
  • Complex query (very long)
  • Server overload

Fix:

  • Reduce limit parameter
  • Simplify query
  • Add tags filter to narrow search scope

Best Practices

  1. Reuse connections: Use HTTP keep-alive for multiple requests
  2. Handle rate limits: Implement exponential backoff when you hit 429
  3. Validate parameters: Check query length and limit range before sending
  4. Store tokens securely: Use environment variables, never commit to git
  5. Monitor usage: Track your API usage to avoid unexpected rate limits

Changelog

  • 2026-01-05: MCP Streamable HTTP support

    • Added initialize method for server capabilities
    • Added tools/list method for tool discovery
    • CORS disabled (CLI/backend access only)
    • Rate limiting per token (McpThrottlerGuard)
    • Protocol version: 2025-06-18
  • 2026-01-01: Initial MCP server release

    • synjar_search tool
    • JSON-RPC 2.0 protocol
    • Rate limiting (100 req/min per IP, 30 req/min per link)
    • Privacy controls (historyMode)


Questions? Contact support or open an issue on GitHub.