# @neuronsearchlab/mcp MCP (Model Context Protocol) server for [NeuronSearchLab](https://www.neuronsearchlab.com). Gives any MCP-compatible AI client (Claude, Codex, Cursor, Windsurf, etc.) direct access to NeuronSearchLab recommendations, product/content search, catalogue operations, analytics, and platform controls. ``` "Get 5 recommendations for user alice@example.com" "Check my current plan and show which resources are over their limits" "Create a new context called Twitter Feed" "Add a pin rule so Nike items always appear in the top 3" "Why did item prod-456 rank first for bob?" ``` Two ways to run it: - **Hosted (recommended, no install):** `https://console.neuronsearchlab.com/api/mcp` — Streamable HTTP with OAuth sign-in or an NSL API key. Listed on the [MCP Registry](https://registry.modelcontextprotocol.io) as `com.neuronsearchlab/mcp` (search `neuronsearchlab` in the registry API or directory). - **Local stdio via npm:** `npx -y @neuronsearchlab/mcp` in two modes — `public` (recommendations, events, catalogue via OAuth client credentials) or `internal` (admin platform via console API key). --- ## Connect to the hosted server (no install) The hosted endpoint runs a submission-safe customer administration profile. It includes first-class tools for plan and usage visibility, ranking configuration, experiments, training, analytics, catalogue inspection, API-key inventory and revocation, integrations, and event types. Every hosted tool declares its OAuth requirement and requires the authenticated team's `admin` scope. Credential creation, billing mutations, and the arbitrary platform API fallback remain available only to trusted local/internal clients so secrets, purchases, and unbounded API calls are not exposed in ChatGPT. Keys minted through OAuth consent appear in [console → Security](https://console.neuronsearchlab.com/security) and can be revoked there anytime. **claude.ai / Claude Desktop** — Settings → Connectors → Add custom connector → paste `https://console.neuronsearchlab.com/api/mcp` → **Connect**, then sign in to your NeuronSearchLab console and approve the scopes. **Claude Code** ```bash # OAuth (browser sign-in): claude mcp add --transport http neuronsearchlab https://console.neuronsearchlab.com/api/mcp # …or with an API key: claude mcp add --transport http neuronsearchlab https://console.neuronsearchlab.com/api/mcp \ --header "Authorization: Bearer nsl_your_key" ``` **OpenAI Codex** — in `~/.codex/config.toml`: ```toml [mcp_servers.neuronsearchlab] url = "https://console.neuronsearchlab.com/api/mcp" bearer_token_env_var = "NSL_API_KEY" ``` **Cursor / Windsurf / other Streamable HTTP clients** ```json { "mcpServers": { "neuronsearchlab": { "url": "https://console.neuronsearchlab.com/api/mcp", "headers": { "Authorization": "Bearer nsl_your_key" } } } } ``` --- ## Resources - Product site: https://www.neuronsearchlab.com - MCP setup guide: https://docs.neuronsearchlab.com/sdk/mcp - AI agents for recommendation operations: https://www.neuronsearchlab.com/blog/ai-agents-for-recommendation-operations - Recommendation systems reading path: https://www.neuronsearchlab.com/blog/recommendation-systems ## Tools ### API tools | Tool | Description | |------|-------------| | `get_recommendations` | Fetch personalised recommendations for a user | | `get_recommendation_sets` | Fetch every rail of a page in one request | | `get_auto_recommendations` | Auto-sectioned feed with pagination (infinite scroll) | | `track_event` | Record a user interaction (click, view, purchase, etc.) | | `upsert_item` | Add or update a catalogue item | | `patch_item` | Partially update an item (enable/disable, change fields) | | `delete_items` | Permanently remove items from the catalogue | | `search_items` | Search the catalogue by keyword | | `explain_ranking` | Explain why an item ranked where it did for a user | ## Modes ### Public mode Uses OAuth client credentials and the public API. Supported: - recommendations - events - catalogue operations ### Internal mode Uses a NeuronSearchLab API key with the `admin` scope against the console API. Currently supported: - account plan and limits: `get_account_plan` returns the effective plan, resolved limits, current usage, and exact overages - catalogue search and ranking debug: `search_items`, `explain_ranking` - contexts: `list_contexts`, `create_context`, `update_context`, `delete_context`, `get_context` - rules: `list_rules`, `create_rule`, `update_rule`, `delete_rule`, `toggle_rule`, `enable_rule`, `disable_rule`, `get_rule` - segments: `list_segments`, `get_segment`, `create_segment`, `update_segment`, `delete_segment` - experiments: `list_experiments`, `get_experiment`, `create_experiment`, `update_experiment`, `start_experiment`, `stop_experiment`, `get_experiment_results`, `refresh_experiment_results` - training: `list_training_jobs`, `get_training_job`, `create_training_job`, `cancel_training_job` - analytics: `get_ranking_metrics`, `get_user_analytics`, `get_item_analytics`, `compare_items`, `top_items` - event types: `list_event_types`, `create_event_type`, `update_event_type`, `delete_event_type` - credentials and integrations: `list_api_keys`, `revoke_api_key`, `list_integrations` (`create_api_key` is local/internal only because it returns credential material) - fallback UI coverage for trusted local/internal clients only: `list_platform_routes`, `call_platform_api` --- ## Quickstart (local stdio) ### 1. Get credentials Generate **SDK Credentials** (OAuth 2.0 client ID + secret) from the [NeuronSearchLab console](https://console.neuronsearchlab.com/security). ### 2. Add to Claude Desktop **Public mode** (recommendations, events, catalogue): Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: ```json { "mcpServers": { "neuronsearchlab": { "command": "npx", "args": ["-y", "@neuronsearchlab/mcp"], "env": { "NSL_CLIENT_ID": "your-client-id", "NSL_CLIENT_SECRET": "your-client-secret" } } } } ``` **Internal mode** (admin platform — contexts, rules, analytics, etc.): ```json { "mcpServers": { "neuronsearchlab": { "command": "npx", "args": ["-y", "@neuronsearchlab/mcp"], "env": { "NSL_PLATFORM_MODE": "internal", "NSL_API_KEY": "your-admin-api-key" } } } } ``` Restart Claude Desktop. You'll see a 🔌 **neuronsearchlab** indicator in the toolbar when it's connected. ### Try it: recommendation-ops demo Once connected, run this named demo path before wiring the server into a larger workflow. It proves that an AI client can operate the recommender as an investigation surface rather than just call a recommendation endpoint. 1. **List contexts** so the assistant confirms the exact surface it is about to inspect. 2. **Fetch recommendations** for a known test user and context. 3. **Search the catalogue** for a concrete product/content phrase and compare the returned item IDs with the recommendation set. 4. **Explain one ranked item** using the `request_id` from the recommendation or search response when available. 5. **Optional, sandbox only:** draft or toggle a rule after the explanation shows the expected leak. Keep production contexts read-only unless the operator explicitly approves a write. ```text Use the NeuronSearchLab MCP server in read-only mode first. List my recommendation contexts and choose the homepage-feed context. Get 10 recommendations for user demo-user@example.com using context homepage-feed. Search the catalogue for waterproof jackets and show the top 5 item ids. Explain why the first recommended item ranked first, using the request_id from the recommendation response if available. If you see a relevance leak, draft the smallest rule that would fix it, but do not create or toggle the rule yet. ``` Internal/admin mode can also inspect operational state before making changes: ```text List ranking rules for the homepage-feed context. Show the latest ranking metrics for that context. Compare item jacket-123 with item jacket-456 and explain which rule or signal separates them. ``` Next steps after the smoke test: - create a scoped API key for the client or MCP server - connect one real recommendation context, such as `homepage-feed` - add request attribution to click/view events before judging ranking quality ### 3. Cursor / other MCP clients Follow your client's MCP server guide. The command is: ``` npx @neuronsearchlab/mcp ``` Set `NSL_CLIENT_ID` + `NSL_CLIENT_SECRET` for public mode, or `NSL_PLATFORM_MODE=internal` + `NSL_API_KEY` for internal mode. --- ## Releases This repo uses Changesets plus GitHub Actions for automated versioning and npm publishing. - Add a changeset for any user-facing package change with `npm run changeset` - Merge that PR into `main` - The `release.yml` workflow opens or updates a version PR - Merging the version PR publishes `@neuronsearchlab/mcp` to npm automatically To enable trusted publishing, configure the package on npmjs.com to trust the `release.yml` workflow in this repository. --- ## Configuration All configuration is via environment variables: | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `NSL_PLATFORM_MODE` | No | `public` | `public` or `internal` | | `NSL_CLIENT_ID` | Public mode | — | OAuth client ID from the console | | `NSL_CLIENT_SECRET` | Public mode | — | OAuth client secret from the console | | `NSL_API_KEY` | Internal mode | — | API key with `admin` scope | | `NSL_TOKEN_URL` | No | `https://auth.neuronsearchlab.com/oauth2/token` | Token endpoint | | `NSL_API_BASE_URL` | No | `https://api.neuronsearchlab.com` in public mode, `https://console.neuronsearchlab.com` in internal mode | API base URL | | `NSL_TIMEOUT_MS` | No | `15000` | Request timeout in milliseconds | --- ## Tool reference ### `get_recommendations` Fetch personalised recommendations for a user. Returns ranked items with scores and a `request_id` for attribution. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `user_id` | string | Yes | User identifier (UUID, email, or any stable string) | | `context_id` | positive integer | No | Context ID generated when the context is created in the console | | `limit` | integer 1–200 | No | Number of items to return (defaults to context value, usually 20) | | `surface` | string | No | Rerank surface override (e.g. `"homepage"`, `"sidebar"`) | **Example** ``` Get 10 recommendations for user alice@example.com using context 101 ``` --- ### `get_recommendation_sets` Fetch every recommendation rail of a page in one request (`POST /v1/recommendations/batch`). Each set is served and billed on its own and carries its own `request_id` for attribution; a set that fails reports its own error. Not exposed in the ChatGPT app profile. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `user_id` | string | One of `user_id`, `anonymous_id` | User identifier | | `anonymous_id` | string | One of `user_id`, `anonymous_id` | Anonymous visitor identifier | | `sets` | array, 1–8 entries | Yes | `{ key, context_id, limit?, item_id?, filters? }` per rail | **Example** ``` Get the homepage rails for alice@example.com: 12 from context 101 as "for_you" and 8 from context 102 as "trending" ``` --- ### `get_auto_recommendations` Fetch the next auto-generated section for a user's feed. Designed for infinite-scroll — each call returns one curated section (e.g. *"Trending this week"*, *"New for you"*) plus a cursor for the next section. Call until `done: true`. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `user_id` | string | Yes | User identifier | | `context_id` | positive integer | No | Optional console-generated context ID | | `limit` | integer 1–200 | No | Items per section | | `cursor` | string | No | Pagination cursor from the previous response | | `window_days` | integer | No | Days to look back for "new" content | --- ### `track_event` Record a user interaction. Always pass `request_id` from the recommendations response to enable click-through attribution. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `event_id` | integer | Yes | Numeric event type ID from the admin console | | `user_id` | string | Yes | User who triggered the event | | `item_id` | positive integer | Yes | NSL-generated item ID returned by ingestion | | `request_id` | string | No | `request_id` from the recommendations response (for attribution) | | `session_id` | string | No | Session identifier for grouping events within a visit | --- ### `upsert_item` Add or update an item in the catalogue. The `description` field is used to generate the embedding — write it to be rich and descriptive. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Display name | | `description` | string | Yes | Rich description for embedding generation | | `metadata` | object | No | Arbitrary key-value pairs returned with recommendations | The item ID is generated by NSL and returned in the ingestion response; callers cannot supply it. --- ### `patch_item` Partially update an existing catalogue item. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `item_id` | positive integer | Yes | NSL-generated item ID to update | | `active` | boolean | No | `false` to exclude from recommendations without deleting | --- ### `delete_items` Permanently remove items. **Cannot be undone.** To temporarily exclude, use `patch_item` with `active: false`. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `item_ids` | positive integer[] (max 100) | Yes | NSL-generated item IDs to delete | --- ### `search_items` Search the catalogue by keyword. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `query` | string | Yes | Text to search for | | `limit` | integer 1–100 | No | Max results (default 20) | --- ### `explain_ranking` Explain why a specific item was ranked at a given position for a user. Returns score breakdown, applied rules, and pipeline trace. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `item_id` | positive integer | Yes | NSL-generated item ID to explain | | `user_id` | string | No | User to score against (omit for neutral baseline) | | `context_id` | positive integer | No | Console-generated context ID to apply scoring rules from | --- ### `list_contexts` List all recommendation contexts (feeds) configured for your team. **Inputs** — none --- ### `create_context` Create a recommendation context: one rail or surface. A context owns its model and its ranking configuration. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `context_name` | string | Yes | Display name (e.g. "Homepage feed") | | `context_key` | string | No | API identifier. Derived from the name if omitted | | `description` | string | No | Optional description | | `recommendation_type` | enum | No | `personalized_discovery`, `related_items`, `search_results`, `continue_watching`, `email_digest`, `merchandising`. Default: `personalized_discovery` | | `model` | object | No | See `model` below. Default: the NSL default model | **Example** ``` Create a new context called "Homepage feed" ``` --- ### `get_context` Get one context: its fields, its `model`, its ranking `configuration` (`template` and serving `stages`) and `configuration_enabled` (whether serving uses that configuration). --- ### `update_context` Update a context. Only the fields you pass change. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `context_id` | integer | Yes | The context ID to update | | `context_name` | string | No | New display name | | `description` | string | No | New description | | `recommendation_type` | enum | No | New recommendation type | | `model` | object | No | The one model this context serves from (below) | | `configuration` | object | No | The complete `{ template, stages }`, as read from `get_context` | | `configuration_enabled` | boolean | No | Whether serving uses the configuration. Off serves platform defaults | `model` is one of: - `{ "kind": "automatic" }`: the NSL default model. - `{ "kind": "nsl", "family": "watch_next", "architecture": "gsasrec" }`: an NSL model deployed to production. - `{ "kind": "trained", "id": 41 }`: a trained model. It supplies its own candidates, so candidate sources and learned reranking do not apply. Personalized discovery and email digest contexts only. --- ### `delete_context` Permanently delete a context, its ranking configuration and its rules. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `context_id` | integer | Yes | The context ID to delete | --- ### `list_rules` List ranking rules, optionally filtered by `context_id`. --- ### `create_rule` Create a ranking rule. Rule types: | Type | Effect | |------|--------| | `boost` | Increase matching items' scores (use `weight` 1.0–5.0) | | `bury` | Decrease matching items' scores (use `weight` 0.0–1.0) | | `pin` | Fix matching items at a specific position (use `pin_position`) | | `filter` | Remove matching items from results | | `cap` | Limit matching items to a fraction of results (use `cap_fraction`) | | `diversity` | Spread items across a field's values (use `diversity_field`, `diversity_max`) | **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Rule display name | | `rule_type` | enum | Yes | `boost`, `bury`, `pin`, `filter`, `cap`, `diversity` | | `conditions` | array | Yes | `[{ field, operator, value }]` — items must match all conditions | | `actions` | object | Yes | `{ type, weight?, pin_position?, cap_fraction?, ... }` | | `context_id` | integer | No | Scope rule to a specific context | | `description` | string | No | Optional description | | `priority` | integer 0–1000 | No | Higher = evaluated first. Default: 100 | **Example** ``` Create a pin rule called "Pin Nike" that pins items where brand equals "Nike" to position 3, scoped to context 1 ``` --- ### `update_rule` / `delete_rule` / `toggle_rule` / `enable_rule` / `disable_rule` Update, delete, or enable/disable a rule by `rule_id`. --- ### `get_user_analytics` Get served counts, event breakdown, unique-item activity, and click-through rate for a specific user. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `user_id` | string | Yes | User ID or email to inspect | | `context_id` | positive integer | No | Scope to a console-generated context | | `window` | `1d` \| `7d` \| `30d` \| `90d` | No | Time window (default `7d`) | --- ### `get_item_analytics` Get served counts, event breakdown, watch/click counts, and click-through rate for a specific item. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `item_id` | positive integer | Yes | NSL-generated item ID to inspect | | `context_id` | positive integer | No | Scope to a console-generated context | | `window` | `1d` \| `7d` \| `30d` \| `90d` | No | Time window (default `7d`) | --- ### `compare_items` Compare two items head-to-head by served count, events, clicks, and CTR over the same time window. **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `item_a_id` | positive integer | Yes | First NSL-generated item ID | | `item_b_id` | positive integer | Yes | Second NSL-generated item ID | | `context_id` | positive integer | No | Scope to a console-generated context | | `window` | `1d` \| `7d` \| `30d` \| `90d` | No | Time window (default `7d`) | --- ### `top_items` List the top items by served count or by matching event activity over a time window. Use `metric="served"` for generic "top item" or "best performing" questions. Use `metric="events"` when the user explicitly names an engagement signal (e.g. watch, click, purchase). **Inputs** | Field | Type | Required | Description | |-------|------|----------|-------------| | `metric` | `served` \| `events` | No | Rank by served count or event count (default `served`) | | `event_name` | string | No | Event name filter when `metric=events` (e.g. `"watch"`, `"click"`) | | `event_id` | integer | No | Numeric event ID filter when `metric=events` | | `context_id` | positive integer | No | Scope to a console-generated context | | `window` | `1d` \| `7d` \| `30d` \| `90d` | No | Time window (default `7d`) | | `limit` | integer 1–50 | No | Max items to return (default 10) | **Example** ``` What's the top item served in the last 7 days? Which items had the most watch events last month? ``` --- ## Authentication **Public mode** uses [OAuth 2.0 Client Credentials](https://console.neuronsearchlab.com/security). Tokens are fetched on startup, cached in memory, and auto-refreshed 60 seconds before expiry. **Internal mode** uses a NeuronSearchLab API key with the `admin` scope. Set `NSL_API_KEY` and `NSL_PLATFORM_MODE=internal`. --- ## Development ```bash git clone https://github.com/NeuronSearchLab/mcp cd mcp npm install export NSL_CLIENT_ID=your-client-id export NSL_CLIENT_SECRET=your-client-secret npm run dev # dev mode (tsx, no build) npm run build # compile to dist/ ``` --- ## License MIT