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.