v0.2.0 — live on npm

Give your LLMs
real context

emcp wraps any MCP server and transforms raw tool responses into typed, pixel-accurate, cross-server context — so your AI agents stop guessing.

Get started → View on GitHub
$ npm install @odin_ssup/emcp
0
Tests Passing
0
Adapters
0.2.0
Version
MIT
License

Raw MCP output forces LLMs to guess

Connect Figma, Notion, or Slack to an LLM through MCP and the model gets unstructured prose. No coordinates, no hex colors, no hierarchy. Broken automations every time.

Before emcp — raw MCP
"Frame 'Dashboard' contains a button
labeled Submit, blue color, top right area"

// No coordinates — "top right" is not x=1142, y=48
// No hex colors — "blue" is not #0D99FF
// No parent-child tree
// No confidence scores
// No cross-server links
After emcp — enriched
{
  "id":         "btn-submit",
  "type":       "BUTTON",
  "spatial": { "x": 1142, "y": 48 },
  "style":   { "fill": "#0D99FF" },
  "parentId":  "frame-dashboard",
  "confidence": { "overall": 0.97 },
  "schema":    "emcp/v1"
}

A 5-layer enrichment pipeline

emcp wraps transparently — no changes to your existing MCP setup.

Layer 0
Raw MCP Servers
Figma, Notion, Slack — untouched source data.
Layer 1
Protocol Adapter
Normalizes stdio, SSE, and HTTP transports.
Layer 2
Context Enrichment
Spatial mapper + semantic tagger + confidence scorer.
Layer 3
Structured Output
JSON envelope, XML tree, or pixel manifest.
Layer 4
Cross-Server Bus
Unified registry with auto-linking and diff tracking.

10 production-ready adapters

Ships with the most common MCP servers. Write your own in ~20 lines.

Figma
Pixel-accurate
Frames, components, text nodes — x, y, w, h, fill, font, z-index, full parent tree.
✓ 12 tests passing
Notion
Semantic
Pages, databases, blocks, rich text, properties, timestamps.
✓ 11 tests passing
Slack
Semantic
Messages, threads, channels, reactions — auto thread linking.
✓ 10 tests passing
Asana
Semantic
Tasks, projects, sections — priority inferred from due dates and custom fields.
✓ 15 tests passing
GitHub
Semantic
Issues, PRs, repos, commits, files — label color as fill, diff stats.
✓ 19 tests passing
Salesforce
Semantic
Opportunities, contacts, accounts, cases, leads — SOQL results enriched.
✓ 15 tests passing
Linear
Semantic
Issues, projects, teams, cycles — priority 1–4 to role, identifier in name.
✓ 14 tests passing
Jira
Semantic
Issues with ADF description parsing, projects, epics — priority + status maps to role.
✓ 18 tests passing
HubSpot
Semantic
Contacts, deals, companies, tickets — deal probability maps to primary/secondary/tertiary role.
✓ 13 tests passing
Airtable
Semantic
Records and tables — status field auto-detected for role, all fields exposed in raw for LLMs.
✓ 13 tests passing
Generic
Fallback
Best-effort fallback for any unrecognized MCP server. Always runs last.
✓ Automatic

Up and running in minutes

Single server, multi-server cross-linking, or custom adapters.

import { EnhancedMCP } from '@odin_ssup/emcp'

const client = new EnhancedMCP({ enrichment: 'full', output: 'json' })

const ctx = await client.process({
  toolName: 'figma_get_file',
  serverId: 'figma',
  content:  rawMCPResponse,
})

console.log(ctx.nodes)          // typed EnrichedNode[], keyed by ID
console.log(ctx.meta.avgConfidence)  // 0.97

const pixels = await client.getPixelManifest()  // sorted by z-index
const xml    = await client.getXML()            // hierarchical tree
const ctx = await client.processMany([
  { toolName: 'figma_get_file',     serverId: 'figma',  content: figmaResponse  },
  { toolName: 'notion-fetch',       serverId: 'notion', content: notionResponse },
  { toolName: 'slack_messages',     serverId: 'slack',  content: slackResponse  },
  { toolName: 'github_list_issues', serverId: 'github', content: githubResponse },
])

// Fuzzy Jaccard similarity auto-links similar names across servers
// "Dashboard" ↔ "Dashboard Page" ↔ "dashboard-main" (threshold: 0.4)

ctx.nodes['frame-dashboard'].linkedNodes
// [{ nodeId: 'notion:page-dashboard', source: 'notion', linkType: 'related' }]
import type { EMCPAdapter, EnrichedNode } from '@odin_ssup/emcp'

export class MyAdapter implements EMCPAdapter {
  name    = 'my-server'
  version = '1.0.0'

  canHandle(toolName: string) {
    return toolName.includes('my_server')
  }

  async parse(toolName: string, response: unknown): Promise<EnrichedNode[]> {
    const raw = response as MyResponseType
    return [{
      id: raw.id, name: raw.name, source: 'my-server', type: 'CONTAINER',
      confidence: { spatial: 0.1, semantic: 0.8, overall: 0.5 },
    }]
  }
}

const client = new EnhancedMCP({ adapters: [new MyAdapter()] })
import { EnhancedMCP } from '@odin_ssup/emcp'

// Process large batches incrementally — no memory spike
const client = new EnhancedMCP()

for await (const ctx of client.processStream(responses)) {
  console.log(`Nodes so far: ${ctx.meta.totalNodes}`)
  // ctx is cumulative — each yield adds to the previous state
  // pipe ctx.nodes to your LLM incrementally
}

// Also accepts AsyncIterable (e.g. a live MCP event stream)
async function* liveSource() {
  for (const r of batch) yield r
}
for await (const ctx of client.processStream(liveSource())) { ... }
import { EnhancedMCP } from '@odin_ssup/emcp'

// Compress context to fit a 4,000-token LLM window
const client = new EnhancedMCP({
  maxTokens: 4000,
  fuzzyLinkingThreshold: 0.5,  // default 0.4
})

await client.processMany(responses)

// getJSON / getXML auto-compress if over budget
// Drop order: decorative → tertiary → secondary → structural → primary (last)
const json = await client.getJSON()  // guaranteed ≤4000 tokens

// Errors are collected — never thrown silently
const ctx = await client.getContext()
if (ctx.errors?.length) {
  ctx.errors.forEach(e => console.warn(e.adapter, e.message))
}

Every field tells the LLM what to trust

Per-field scores so your agent never has to guess about data quality.

0.99
Exact from API
Figma bounding box coordinates
0.80–0.95
Strongly inferred
Semantic type from Figma node type
0.50–0.79
Inferred from hints
Button type from name "Submit Btn"
0.10–0.49
Guessed / fallback
Generic adapter best-effort parse