@symbo.ls/mcp 1.0.6 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  mcp-name: io.github.symbo-ls/symbols-mcp
4
4
 
5
- MCP server for [Symbols/DOMQL v3](https://symbols.so) — provides documentation search and framework reference tools for AI coding assistants (Cursor, Claude Code, Windsurf, etc.).
5
+ MCP server for [Symbols.app](https://symbols.app) — provides documentation search and framework reference tools for AI coding assistants (Cursor, Claude Code, Windsurf, etc.).
6
6
 
7
7
  No API keys required. All tools work fully offline using bundled documentation.
8
8
 
@@ -10,7 +10,7 @@ No API keys required. All tools work fully offline using bundled documentation.
10
10
 
11
11
  ## Tools
12
12
 
13
- - **`get_project_rules`** — Returns mandatory Symbols/DOMQL v3 rules. Call this before any code generation task.
13
+ - **`get_project_rules`** — Returns mandatory Symbols.app rules. Call this before any code generation task.
14
14
  - **`search_symbols_docs`** — Keyword search across all bundled Symbols documentation files.
15
15
 
16
16
  ## Resources
@@ -1,17 +1,101 @@
1
1
  #!/usr/bin/env node
2
- const { spawn } = require('child_process')
3
- const { execSync } = require('child_process')
4
-
5
- let cmd, args
6
-
7
- try {
8
- execSync('uvx --version', { stdio: 'ignore' })
9
- cmd = 'uvx'
10
- args = ['symbols-mcp']
11
- } catch {
12
- cmd = 'python3'
13
- args = ['-m', 'symbols_mcp.server']
2
+ 'use strict'
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const readline = require('readline')
6
+
7
+ const SKILLS_DIR = path.join(__dirname, '..', 'symbols_mcp', 'skills')
8
+
9
+ function readSkill(filename) {
10
+ const p = path.join(SKILLS_DIR, filename)
11
+ return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : `Skill '${filename}' not found`
12
+ }
13
+
14
+ function loadAgentInstructions() {
15
+ const ai = path.join(SKILLS_DIR, 'AGENT_INSTRUCTIONS.md')
16
+ return fs.existsSync(ai) ? fs.readFileSync(ai, 'utf8') : readSkill('CLAUDE.md')
17
+ }
18
+
19
+ function searchDocs(query, maxResults = 3) {
20
+ const keywords = query.toLowerCase().split(/\s+/).filter(w => w.length > 2)
21
+ if (!keywords.length) keywords.push(query.toLowerCase())
22
+
23
+ const results = []
24
+ for (const fname of fs.readdirSync(SKILLS_DIR)) {
25
+ if (!fname.endsWith('.md')) continue
26
+ const content = fs.readFileSync(path.join(SKILLS_DIR, fname), 'utf8')
27
+ if (!keywords.some(kw => content.toLowerCase().includes(kw))) continue
28
+ const lines = content.split('\n')
29
+ for (let i = 0; i < lines.length; i++) {
30
+ if (keywords.some(kw => lines[i].toLowerCase().includes(kw))) {
31
+ results.push({ file: fname, snippet: lines.slice(Math.max(0, i - 2), Math.min(lines.length, i + 20)).join('\n') })
32
+ break
33
+ }
34
+ }
35
+ if (results.length >= maxResults) break
36
+ }
37
+ return results.length ? JSON.stringify(results, null, 2) : `No results found for '${query}'`
38
+ }
39
+
40
+ const TOOLS = [
41
+ {
42
+ name: 'get_project_rules',
43
+ description: 'ALWAYS call this first before any generate_* tool. Returns the mandatory Symbols.app rules that MUST be followed. Violations cause silent failures — black page, nothing renders.',
44
+ inputSchema: { type: 'object', properties: {} }
45
+ },
46
+ {
47
+ name: 'search_symbols_docs',
48
+ description: 'Search the Symbols documentation knowledge base for relevant information.',
49
+ inputSchema: {
50
+ type: 'object',
51
+ properties: {
52
+ query: { type: 'string', description: 'Natural language search query about Symbols/DOMQL' },
53
+ max_results: { type: 'number', description: 'Maximum number of results (1-5)', default: 3 }
54
+ },
55
+ required: ['query']
56
+ }
57
+ }
58
+ ]
59
+
60
+ function send(obj) {
61
+ process.stdout.write(JSON.stringify(obj) + '\n')
62
+ }
63
+
64
+ function handle(req) {
65
+ if (!req.method) return
66
+ if (req.method === 'initialize') {
67
+ return send({
68
+ jsonrpc: '2.0', id: req.id,
69
+ result: {
70
+ protocolVersion: req.params?.protocolVersion ?? '2025-03-26',
71
+ capabilities: { tools: {} },
72
+ serverInfo: { name: 'Symbols MCP', version: '1.0.6' }
73
+ }
74
+ })
75
+ }
76
+ if (req.method === 'notifications/initialized') return
77
+ if (req.method === 'ping') return send({ jsonrpc: '2.0', id: req.id, result: {} })
78
+ if (req.method === 'tools/list') return send({ jsonrpc: '2.0', id: req.id, result: { tools: TOOLS } })
79
+ if (req.method === 'tools/call') {
80
+ const { name, arguments: args = {} } = req.params
81
+ try {
82
+ let text
83
+ if (name === 'get_project_rules') text = loadAgentInstructions()
84
+ else if (name === 'search_symbols_docs') text = searchDocs(args.query, args.max_results || 3)
85
+ else throw new Error(`Unknown tool: ${name}`)
86
+ return send({ jsonrpc: '2.0', id: req.id, result: { content: [{ type: 'text', text }] } })
87
+ } catch (e) {
88
+ return send({ jsonrpc: '2.0', id: req.id, result: { content: [{ type: 'text', text: e.message }], isError: true } })
89
+ }
90
+ }
91
+ if (req.id !== undefined) {
92
+ send({ jsonrpc: '2.0', id: req.id, error: { code: -32601, message: 'Method not found' } })
93
+ }
14
94
  }
15
95
 
16
- const child = spawn(cmd, args, { stdio: 'inherit' })
17
- child.on('exit', code => process.exit(code ?? 0))
96
+ const rl = readline.createInterface({ input: process.stdin, terminal: false })
97
+ rl.on('line', line => {
98
+ if (!line.trim()) return
99
+ try { handle(JSON.parse(line)) } catch (e) { process.stderr.write(`Parse error: ${e.message}\n`) }
100
+ })
101
+ rl.on('close', () => process.exit(0))
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@symbo.ls/mcp",
3
- "version": "1.0.6",
4
- "description": "MCP server for Symbols/DOMQL v3 — documentation search and framework reference",
3
+ "version": "1.0.9",
4
+ "description": "MCP server for Symbols.app — documentation search and framework reference",
5
5
  "mcpName": "io.github.symbo-ls/symbols-mcp",
6
+ "files": [
7
+ "bin/",
8
+ "symbols_mcp/skills/"
9
+ ],
6
10
  "bin": {
7
11
  "symbols-mcp": "./bin/symbols-mcp.js"
8
12
  },
@@ -10,6 +14,12 @@
10
14
  "type": "git",
11
15
  "url": "https://github.com/symbo-ls/symbols-mcp.git"
12
16
  },
13
- "keywords": ["mcp", "symbols", "domql", "ai", "claude"],
17
+ "keywords": [
18
+ "mcp",
19
+ "symbols",
20
+ "domql",
21
+ "ai",
22
+ "claude"
23
+ ],
14
24
  "license": "MIT"
15
25
  }
@@ -1,6 +1,6 @@
1
1
  # Symbols / DOMQL v3 — AI Agent Instructions
2
2
 
3
- You are working inside a **Symbols/DOMQL v3** project. These rules are absolute and override any general coding instincts. Violations cause silent failures (black page, nothing renders).
3
+ You are working inside a **Symbols.app** project. These rules are absolute and override any general coding instincts. Violations cause silent failures (black page, nothing renders).
4
4
 
5
5
  ---
6
6
 
@@ -8,10 +8,10 @@ You are working inside a **Symbols/DOMQL v3** project. These rules are absolute
8
8
 
9
9
  ```js
10
10
  // CORRECT
11
- export const Header = { extends: 'Flex', padding: 'A' }
11
+ export const Header = { extends: "Flex", padding: "A" };
12
12
 
13
13
  // WRONG — never
14
- export const Header = (el, state) => ({ padding: 'A' })
14
+ export const Header = (el, state) => ({ padding: "A" });
15
15
  ```
16
16
 
17
17
  ---
@@ -36,11 +36,11 @@ Nav: { extends: 'Navbar' }
36
36
 
37
37
  ```js
38
38
  // CORRECT
39
- export * from './Navbar.js'
40
- export * from './PostCard.js'
39
+ export * from "./Navbar.js";
40
+ export * from "./PostCard.js";
41
41
 
42
42
  // WRONG — this breaks everything
43
- export * as Navbar from './Navbar.js'
43
+ export * as Navbar from "./Navbar.js";
44
44
  ```
45
45
 
46
46
  ---
@@ -60,8 +60,8 @@ export const main = { extends: 'Flex', ... }
60
60
  ## 5. `pages/index.js` — imports ARE allowed here (it's the registry)
61
61
 
62
62
  ```js
63
- import { main } from './main.js'
64
- export default { '/': main }
63
+ import { main } from "./main.js";
64
+ export default { "/": main };
65
65
  ```
66
66
 
67
67
  This is the **only** file where cross-file imports are permitted.
@@ -96,12 +96,12 @@ export const switchView = function switchView(view) {
96
96
  ```js
97
97
  // functions/myFn.js
98
98
  export const myFn = function myFn(arg1) {
99
- const node = this.node // 'this' is the DOMQL element
100
- }
99
+ const node = this.node; // 'this' is the DOMQL element
100
+ };
101
101
 
102
102
  // In component
103
- onClick: (e, el) => el.call('myFn', someArg) // CORRECT
104
- onClick: (e, el) => el.call('myFn', el, someArg) // WRONG — el passed twice
103
+ onClick: (e, el) => el.call("myFn", someArg); // CORRECT
104
+ onClick: (e, el) => el.call("myFn", el, someArg); // WRONG — el passed twice
105
105
  ```
106
106
 
107
107
  ---
@@ -136,8 +136,10 @@ MyBtn: { extends: 'Button', Icon: { name: 'heart' } }
136
136
  ## 10. State — use `s.update()`, never mutate directly
137
137
 
138
138
  ```js
139
- onClick: (e, el, s) => s.update({ count: s.count + 1 }) // CORRECT
140
- onClick: (e, el, s) => { s.count = s.count + 1 } // WRONG — no re-render
139
+ onClick: (e, el, s) => s.update({ count: s.count + 1 }); // CORRECT
140
+ onClick: (e, el, s) => {
141
+ s.count = s.count + 1;
142
+ }; // WRONG — no re-render
141
143
  ```
142
144
 
143
145
  Root-level state (global): `s.root.update({ key: val })`
@@ -146,11 +148,11 @@ Root-level state (global): `s.root.update({ key: val })`
146
148
 
147
149
  ## 11. v3 syntax — never use v2
148
150
 
149
- | v3 ✅ | v2 ❌ |
150
- |---|---|
151
- | `extends: 'X'` | `extend: 'X'` |
152
- | `childExtends: 'X'` | `childExtend: 'X'` |
153
- | `onClick: fn` | `on: { click: fn }` |
151
+ | v3 ✅ | v2 ❌ |
152
+ | ----------------------- | ------------------------ |
153
+ | `extends: 'X'` | `extend: 'X'` |
154
+ | `childExtends: 'X'` | `childExtend: 'X'` |
155
+ | `onClick: fn` | `on: { click: fn }` |
154
156
  | props flattened at root | `props: { ... }` wrapper |
155
157
 
156
158
  ---
@@ -168,10 +170,10 @@ components/nav/Navbar.js ❌
168
170
 
169
171
  ```js
170
172
  onRender: (el) => {
171
- if (el.__initialized) return
172
- el.__initialized = true
173
+ if (el.__initialized) return;
174
+ el.__initialized = true;
173
175
  // imperative logic here
174
- }
176
+ };
175
177
  ```
176
178
 
177
179
  ---
@@ -205,17 +207,23 @@ Define named tokens in `designSystem/COLOR.js` instead:
205
207
  ```js
206
208
  // designSystem/COLOR.js
207
209
  export default {
208
- whiteMuted: 'rgba(255,255,255,0.7)',
209
- whiteSubtle: 'rgba(255,255,255,0.6)',
210
- whiteFaint: 'rgba(255,255,255,0.5)',
211
- }
210
+ whiteMuted: "rgba(255,255,255,0.7)",
211
+ whiteSubtle: "rgba(255,255,255,0.6)",
212
+ whiteFaint: "rgba(255,255,255,0.5)",
213
+ };
212
214
 
213
215
  // In component — CORRECT
214
- { color: 'whiteMuted' }
216
+ {
217
+ color: "whiteMuted";
218
+ }
215
219
 
216
220
  // WRONG — renders as literal text, not a style
217
- { color: 'white .7' }
218
- { color: 'black .5' }
221
+ {
222
+ color: "white .7";
223
+ }
224
+ {
225
+ color: "black .5";
226
+ }
219
227
  ```
220
228
 
221
229
  ---
@@ -1,6 +1,6 @@
1
1
  # Built-in atoms and property usage
2
2
 
3
- This document lists the built-in components (atoms and UI kit components) and how their properties are commonly used in Symbols/DOMQL v3.
3
+ This document lists the built-in components (atoms and UI kit components) and how their properties are commonly used in Symbols.app.
4
4
 
5
5
  ## Built-in atoms
6
6