🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@lattis-dev/cli

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lattis-dev/cli - npm Package Compare versions

Comparing version
0.1.1
to
0.2.0
+137
dist/mcp.js
#!/usr/bin/env node
/**
* Lattis MCP Server
*
* Exposes Lattis data to AI agents via Model Context Protocol.
* Works with Claude Code, Cursor, and any MCP client.
*
* Usage:
* npx @lattis-dev/cli mcp (via CLI)
* lattis-mcp (standalone binary)
*
* Claude Code config (~/.claude/settings.json):
* {
* "mcpServers": {
* "lattis": {
* "command": "npx",
* "args": ["-y", "@lattis-dev/cli", "mcp"]
* }
* }
* }
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API = process.env.LATTIS_API_URL ?? "https://api.lattis.dev";
async function api(path, options) {
let resp;
try {
resp = await fetch(`${API}${path}`, {
headers: { "Content-Type": "application/json" },
...options,
});
}
catch (err) {
throw new Error(`API unreachable: ${err instanceof Error ? err.message : String(err)}`);
}
if (!resp.ok) {
const text = await resp.text().catch(() => "");
throw new Error(`API ${path} returned ${resp.status}: ${text}`);
}
return resp.json();
}
function errorResult(message) {
return { content: [{ type: "text", text: message }], isError: true };
}
const server = new McpServer({
name: "lattis",
version: "0.2.0",
});
server.tool("lattis_list_sites", "List all websites indexed by Lattis. Returns site name, category, item count, and URL for each indexed site.", {}, async () => {
try {
const data = await api("/sites");
const sites = data.sites.map((s) => ({
domain: s.domain,
name: s.analysis?.name ?? s.domain,
category: s.analysis?.category ?? "unknown",
items: s.item_count,
url: s.url,
status: s.status,
}));
return { content: [{ type: "text", text: JSON.stringify(sites, null, 2) }] };
}
catch (err) {
return errorResult(`Failed to list sites: ${err instanceof Error ? err.message : String(err)}`);
}
});
server.tool("lattis_search", "Search across all indexed websites for facts, capabilities, and actions. Returns ranked results with source URLs. Use this to find real data about products, services, pricing, and how to accomplish tasks.", {
query: z.string().describe("Search query, e.g. 'analytics for startups' or 'book a meeting'"),
site: z.string().optional().describe("Optional: limit search to a specific domain"),
type: z.string().optional().describe("Optional: filter by 'fact', 'capability', or 'action'"),
}, async ({ query, site, type }) => {
try {
const body = { q: query };
if (site)
body.site = site;
if (type)
body.type = type;
const data = await api("/search", { method: "POST", body: JSON.stringify(body) });
if (data.results.length === 0) {
return { content: [{ type: "text", text: `No results for "${query}"` }] };
}
const output = data.results.map((r) => ({
type: r.type,
text: r.text,
site: r.site_name,
domain: r.domain,
url: r.url,
}));
return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
}
catch (err) {
return errorResult(`Search failed: ${err instanceof Error ? err.message : String(err)}`);
}
});
server.tool("lattis_get_items", "Get all extracted items (facts, capabilities, actions) for a specific website. Use to understand what a site offers, its pricing, features, and how to use it.", {
domain: z.string().describe("Domain, e.g. 'posthog.com' or 'cal.com'"),
type: z.string().optional().describe("Optional: filter by 'fact', 'capability', or 'action'"),
}, async ({ domain, type }) => {
try {
const typeParam = type ? `?type=${type}` : "";
const data = await api(`/sites/${domain}/items${typeParam}`);
if (data.items.length === 0) {
return { content: [{ type: "text", text: `No items for "${domain}". Use lattis_list_sites to see available sites.` }] };
}
return { content: [{ type: "text", text: JSON.stringify(data.items, null, 2) }] };
}
catch (err) {
return errorResult(`Failed to get items: ${err instanceof Error ? err.message : String(err)}`);
}
});
server.tool("lattis_get_page", "Get the full markdown content of a specific page from an indexed website.", {
domain: z.string().describe("Domain, e.g. 'posthog.com'"),
path: z.string().describe("Page path, e.g. '/pricing' or '/about'"),
}, async ({ domain, path }) => {
try {
const data = await api(`/sites/${domain}/pages${path}`);
if (data.error) {
const available = data.available?.map((p) => p.path).join(", ") ?? "none";
return { content: [{ type: "text", text: `Page not found: ${path}. Available: ${available}` }] };
}
const page = data.page;
return { content: [{ type: "text", text: `# ${page.title}\nURL: ${page.url}\n\n${page.markdown}` }] };
}
catch (err) {
return errorResult(`Failed to get page: ${err instanceof Error ? err.message : String(err)}`);
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Lattis MCP server running on stdio");
console.error(`API: ${API}`);
}
main().catch((err) => {
console.error("Fatal:", err);
process.exit(1);
});
+5
-1
#!/usr/bin/env node
#!/usr/bin/env node
import { api } from "./api.js";

@@ -138,2 +137,3 @@ let agentMode = false;

lattis analyze <url> Submit a site for indexing
lattis mcp Start MCP server (for Claude Code, Cursor)

@@ -186,2 +186,6 @@ Flags:

break;
case "mcp":
// Import and run MCP server
await import("./mcp.js");
break;
default:

@@ -188,0 +192,0 @@ // Treat unknown command as search

+8
-3
{
"name": "@lattis-dev/cli",
"version": "0.1.1",
"version": "0.2.0",
"repository": {

@@ -12,3 +12,4 @@ "type": "git",

"bin": {
"lattis": "./dist/cli.js"
"lattis": "./dist/cli.js",
"lattis-mcp": "./dist/mcp.js"
},

@@ -20,3 +21,3 @@ "files": [

"scripts": {
"build": "tsc && node -e \"const fs=require('fs');const f='dist/cli.js';fs.writeFileSync(f,'#!/usr/bin/env node\\n'+fs.readFileSync(f,'utf8'));fs.chmodSync(f,0o755)\"",
"build": "tsc && node -e \"const fs=require('fs');['dist/cli.js','dist/mcp.js'].forEach(f=>{if(!fs.existsSync(f))return;const c=fs.readFileSync(f,'utf8');if(!c.startsWith('#!'))fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c);fs.chmodSync(f,0o755)})\"",
"dev": "tsx src/cli.ts",

@@ -35,2 +36,6 @@ "prepublishOnly": "npm run build"

"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1",
"zod": "^3.23.0"
},
"devDependencies": {

@@ -37,0 +42,0 @@ "@types/node": "^22",

@@ -25,4 +25,44 @@ # @lattis-dev/cli

## Usage
## Use with Claude Code / Cursor (MCP)
Add to your Claude Code settings (`~/.claude/settings.json`):
```json
{
"mcpServers": {
"lattis": {
"command": "npx",
"args": ["-y", "@lattis-dev/cli", "mcp"]
}
}
}
```
Or for Cursor, add to `.cursor/mcp.json`:
```json
{
"mcpServers": {
"lattis": {
"command": "npx",
"args": ["-y", "@lattis-dev/cli", "mcp"]
}
}
}
```
This gives your AI 4 tools: `lattis_search`, `lattis_list_sites`, `lattis_get_items`, `lattis_get_page`.
## Use with any AI (pipe mode)
```bash
# Pipe structured JSON into your agent
lattis search "PostHog pricing" --agent | your-agent
# Or use as context in a prompt
echo "Based on this data: $(lattis site posthog.com --agent)" | llm
```
## CLI Commands
### Search across all indexed sites

@@ -29,0 +69,0 @@