npm package: @lynkt-ai/mcp-proxy
Description: MCP server for Lynkt knowledge graph integration with Claude Desktop
Author: Lynkt Team
Latest version: 0.0.1

# Lynkt Learning Service MCP Proxy

A Model Context Protocol (MCP) server that connects Claude Desktop directly to Lynkt's LearningService over Connect RPC. The proxy exposes rich tools for clarifications, feedback capture, ontology enhancements, streaming updates, and analytics while handling authentication, metadata, and structured MCP responses.

## Configuration

The proxy reads its configuration from environment variables. Each tool call automatically attaches request metadata using the values provided here.

| Variable | Required | Description |
|----------|----------|-------------|
| `LEARNING_SERVICE_URL` | ✅ | Base URL of the LearningService Connect endpoint (e.g. `https://learning.dev.lynkt.de`) |
| `LYNKT_ACCESS_TOKEN` | ✅ | Auth0 bearer token granting access to the service |
| `LYNKT_TENANT_ID` | ✅ | Tenant identifier used for request metadata |
| `LYNKT_ACTOR_ID` | ✅ | Actor (user/service) identifier used for request metadata |
| `LYNKT_LOG_LEVEL` | ⛔️ | Optional log verbosity (`error`, `warn`, `info`, `debug`) |

> **Tip:** Claude Desktop can reference `.env` files using the `envFile` field in `claude_desktop_config.json` if you prefer not to hard-code tokens.

### Claude Desktop Example

```json
{
  "mcpServers": {
    "lynkt-learning": {
      "command": "npx",
      "args": ["-y", "mcp-server-lynkt"],
      "env": {
        "LEARNING_SERVICE_URL": "https://learning.dev.lynkt.de",
        "LYNKT_ACCESS_TOKEN": "your-auth0-token",
        "LYNKT_TENANT_ID": "tenant-123",
        "LYNKT_ACTOR_ID": "user@example.com",
        "LYNKT_LOG_LEVEL": "info"
      }
    }
  }
}
```

After updating the configuration restart Claude Desktop to load the new MCP server.

## Available Tools

All tools accept JSON arguments. Field names use `snake_case` (proto style) or `camelCase` interchangeably. Each tool returns a textual summary plus a JSON payload containing the raw response, including audit and obligation metadata.

### `learning_request_clarification`
Request a clarification when ambiguity is detected.

```json
{
  "ambiguity": {
    "ambiguityId": "amb-42",
    "type": "INSUFFICIENT_CONTEXT",
    "originalQuery": "Schedule ML session",
    "ambiguousTerms": ["ML"],
    "candidates": [
      {"concept": {"type": "concept", "id": "machine-learning"}, "confidence": 0.64}
    ]
  },
  "timeout_seconds": 900
}
```

### `learning_submit_clarification`
Submit a user's response to a clarification request.

```json
{
  "clarification_response": {
    "clarificationId": "amb-42",
    "selectedOptionIds": ["machine-learning"],
    "freeformInput": "We meant supervised learning"
  }
}
```

### `learning_capture_feedback`
Persist an explicit feedback entry linking an ambiguous term to a resolved concept.

```json
{
  "feedback": {
    "ambiguousTerm": "AI",
    "resolvedConcept": {"type": "concept", "id": "artificial-intelligence"},
    "originalQuery": "AI onboarding",
    "confidence": 0.95,
    "context": {"synonyms": ["AI", "artificial intelligence"]}
  },
  "auto_create_learning_task": true
}
```

### `learning_enhance_ontology`
Trigger an ontology enhancement task.

```json
{
  "task_id": "task-123",
  "apply_immediately": true
}
```

### `learning_get_history`
Fetch historical feedback, tasks, and metrics.

```json
{
  "concept_ids": ["artificial-intelligence"],
  "include_tasks": true,
  "include_metrics": true
}
```

### `learning_stream_updates`
Subscribe to server-streamed learning events. Notifications arrive asynchronously.

```json
{
  "concept_ids": ["artificial-intelligence"],
  "task_statuses": ["PROCESSING", "COMPLETED"],
  "timeout_ms": 60000
}
```
Use `learning_cancel_stream` with the returned `streamId` to cancel early.

### `learning_cancel_stream`
Cancel an active stream.

```json
{
  "stream_id": "claude-desktop-123-5"
}
```

### `learning_approve_task`
Approve or reject a human-in-the-loop learning task.

```json
{
  "task_id": "task-123",
  "approved": true,
  "approval_note": "Reviewed during weekly triage"
}
```

### `learning_get_metrics`
Retrieve aggregated learning loop metrics.

```json
{
  "group_by_tenant": true,
  "tenant_ids": ["tenant-123", "tenant-456"]
}
```

### `learning_loop` (deprecated)
Legacy wrapper that mirrors the historical tool. Prefer `learning_capture_feedback` for new workflows.

```json
{
  "synonyms": ["AI", "machine intelligence"],
  "notes": "Common phrasing in marketing copy"
}
```

### `learning_define_glossary_term`
Define or update a glossary term with definition, examples, and aliases.

```json
{
  "term": "Machine Learning",
  "definition": "A subset of AI that enables systems to learn from data without explicit programming",
  "examples": [
    "We use machine learning for recommendation systems",
    "Machine learning models improve with more training data"
  ],
  "aliases": ["ML", "statistical learning"]
}
```

### `learning_link_glossary_terms`
Create a relationship between two glossary terms.

```json
{
  "source_term": "Machine Learning",
  "target_term": "Artificial Intelligence",
  "relationship_type": "specializes",
  "reason": "ML is a specialized subset of AI"
}
```

### `learning_find_term_definition`
Search for a glossary term definition and optionally retrieve related terms.

```json
{
  "term": "Machine Learning",
  "include_related_terms": true
}
```

### `learning_store_context`
Store or update contextual knowledge about a concept. Use this to save insights, notes, metadata, or any other contextual information.

```json
{
  "contribution": {
    "collaborator_id": "data-science-team",
    "concept": {
      "type": "concept",
      "id": "machine-learning"
    },
    "payload_type": "note",
    "payload": {
      "insight": "Works best with normalized features",
      "sensitivity": "internal",
      "tags": ["best-practice", "data-prep"]
    }
  },
  "collaborator_label": "Data Science Team"
}
```

### `learning_get_aggregated_context`
Get the aggregated context for a concept as a canonical graph structure. Returns context in a **node-link graph format** that clearly shows provenance and relationships.

**Request:**
```json
{
  "concept_id": "machine-learning",
  "as_of": "2025-01-15T10:00:00Z"
}
```

**Response Format:**

The MCP proxy transforms the backend response into a well-organized graph structure with four main components:

1. **Root** - The primary concept being queried
2. **Concepts** - Related child concepts referenced by the root
3. **Contributions** - Individual knowledge contributions with full provenance
4. **Edges** - Relationships connecting concepts and contributions

**Example Response:**
```json
{
  "graph": {
    "root": {
      "id": "machine-learning",
      "type": "concept",
      "label": "Machine Learning",
      "is_root": true
    },
    "concepts": [
      {
        "id": "deep-learning",
        "type": "concept",
        "label": "Deep Learning",
        "is_root": false
      }
    ],
    "contributions": [
      {
        "id": "contrib-123",
        "type": "contribution",
        "concept_id": "machine-learning",
        "contributor_id": "data-science-team",
        "contributor_label": "Data Science Team",
        "payload_type": "best-practice",
        "payload": {
          "recommendation": "Always normalize features",
          "rationale": "Improves model convergence"
        },
        "digest": "a1b2c3d4...",
        "contributed_at": "2025-01-10T14:30:00Z",
        "updated_at": "2025-01-10T14:30:00Z"
      },
      {
        "id": "contrib-456",
        "type": "contribution",
        "concept_id": "deep-learning",
        "contributor_id": "platform-team",
        "contributor_label": "Platform Team",
        "payload_type": "infrastructure",
        "payload": {
          "deployment": "kubernetes",
          "scaling": "horizontal"
        },
        "digest": "e5f6g7h8...",
        "contributed_at": "2025-01-12T09:15:00Z",
        "updated_at": "2025-01-12T09:15:00Z"
      }
    ],
    "edges": [
      {
        "id": "rel-789",
        "source": "deep-learning",
        "target": "machine-learning",
        "relationship_type": "CONTEXT_RELATIONSHIP_TYPE_REFINES",
        "sequence": 1,
        "is_transitive": false,
        "transitive_chain": [],
        "metadata": { "confidence": 0.95 },
        "created_by": "platform-team",
        "updated_by": "platform-team",
        "created_at": "2025-01-12T09:20:00Z",
        "updated_at": "2025-01-12T09:20:00Z"
      }
    ]
  },
  "metadata": {
    "mergeRevision": "rev-42",
    "snapshotAt": "2025-01-15T10:00:00Z"
  }
}
```

**Why This Format?**

This organized graph format makes complex knowledge structures easy to understand:
- **Clear Hierarchy**: Root concept separate from child concepts
- **Complete Provenance**: Each contribution shows concept, contributor, timing, and content
- **Visible Relationships**: Edges explicitly show how concepts and contributions connect
- **Transitive Clarity**: Can see direct vs. computed relationships
- **Metadata Preserved**: Custom relationship metadata included
- **Graph-Ready**: Direct input to visualization tools (D3.js, Cytoscape, Neo4j)
- **LLM-Friendly**: Structured for easy reasoning about knowledge graphs

### `learning_list_contributions`
List context contributions with filtering.

```json
{
  "concept_ids": ["machine-learning", "deep-learning"],
  "collaborator_ids": ["data-science-team"],
  "payload_types": ["governance-annotation"],
  "pagination": {
    "pageSize": 20
  }
}
```

### `learning_create_context_relationship`
Create a directional relationship between two resources (contexts or concepts). Use this to explicitly link related knowledge.

**Relationship Types:**

- `references` - Context A mentions or cites Context/Concept B
- `depends_on` - Context A requires Context/Concept B to be understood
- `conflicts_with` - Context A contradicts Context/Concept B
- `builds_upon` - Context A extends or enhances Context/Concept B
- `replaces` - Context A supersedes or obsoletes Context/Concept B
- `explained_by` - Context A is clarified by Context/Concept B
- `refines` - Context A provides more detail about Context/Concept B

**Example 1: Link a glossary term to a concept**
```json
{
  "source_id": "machine-learning",
  "target_id": "artificial-intelligence",
  "relationship_type": "refines",
  "change_notes": "ML is a specialized subset of AI that refines the general concept"
}
```

**Example 2: Mark conflicting approaches**
```json
{
  "source_id": "contribution-123",
  "target_id": "contribution-456",
  "relationship_type": "conflicts_with",
  "relationship_metadata": {
    "reason": "These approaches are mutually exclusive",
    "confidence": 0.95
  },
  "change_notes": "Identified during architecture review"
}
```

**Example 3: Link documentation that builds on previous work**
```json
{
  "source_id": "advanced-ml-guide",
  "target_id": "ml-basics",
  "relationship_type": "builds_upon",
  "change_notes": "Advanced guide assumes knowledge from basics"
}
```

**Example 4: Mark superseded documentation**
```json
{
  "source_id": "api-v2-docs",
  "target_id": "api-v1-docs",
  "relationship_type": "replaces",
  "change_notes": "API v2 supersedes v1"
}
```

## Streaming Notifications

`learning_stream_updates` responses include a `streamId`. While the subscription is active, the proxy emits `notifications/message` payloads describing each update, including policy obligations or audit records supplied by the service. Cancel the stream by calling `learning_cancel_stream` or by letting the timeout elapse.

## Health Check Utility

`npm run test:health` executes `test-connection-enhanced.js`, which creates a gRPC client, performs a metadata-authenticated health probe, and issues a sample unary RPC. Ensure the same environment variables used for Claude Desktop are present when running the script locally.

```bash
LEARNING_SERVICE_URL=https://learning.dev.lynkt.de \
LYNKT_ACCESS_TOKEN=... \
LYNKT_TENANT_ID=tenant-123 \
LYNKT_ACTOR_ID=user@example.com \
npm run test:health
```

## Development Workflow

### Local Development with Localhost Server

1. **Start your local LearningService** (from the `service` directory):
   ```bash
   cd ../service
   go run server/cmd/main.go
   ```
   The service will start on `http://localhost:8080`

2. **Configure the MCP proxy for localhost**:
   ```bash
   cd mcp-proxy
   cp .env.example .env
   # Edit .env to ensure LEARNING_SERVICE_URL=http://localhost:8080
   ```

3. **Run the MCP proxy**:
   ```bash
   export LEARNING_SERVICE_URL=http://localhost:8080
   export LYNKT_ACCESS_TOKEN=dev-token-bypass-for-local-testing
   export LYNKT_TENANT_ID=test-tenant
   export LYNKT_ACTOR_ID=test-actor
   export LYNKT_LOG_LEVEL=debug

   node enhanced-proxy.js
   ```

4. **Test with Claude Desktop** (optional):
   Update your `claude_desktop_config.json` to point to localhost:
   ```json
   {
     "mcpServers": {
       "lynkt-learning-local": {
         "command": "node",
         "args": ["/absolute/path/to/mcp-proxy/enhanced-proxy.js"],
         "env": {
           "LEARNING_SERVICE_URL": "http://localhost:8080",
           "LYNKT_ACCESS_TOKEN": "dev-token",
           "LYNKT_TENANT_ID": "test-tenant",
           "LYNKT_ACTOR_ID": "test-actor",
           "LYNKT_LOG_LEVEL": "debug"
         }
       }
     }
   }
   ```

### Standard Development Tasks

1. Install dependencies: `npm install`
2. Run unit tests: `npm test`
3. Optional health probe against a live service: `npm run test:health`
4. Regenerate protobuf stubs after schema changes:
   ```bash
   npm run generate:learning
   ```
   This command expects [`buf`](https://buf.build/docs/introduction) to be installed and runs inside the repository so that generated files land in `mcp-proxy/gen`.

Ensure CI pipelines execute `npm test` so that the MCP proxy unit tests run automatically.