@symbo.ls/mcp 1.0.5 → 1.0.8
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 +2 -2
- package/bin/symbols-mcp.js +98 -14
- package/package.json +13 -3
- package/symbols_mcp/skills/AGENT_INSTRUCTIONS.md +37 -29
- package/symbols_mcp/skills/BUILT_IN_COMPONENTS.md +1 -1
- package/symbols_mcp/skills/DEFAULT_COMPONENTS.md +132 -69
- package/symbols_mcp/skills/DEFAULT_DESIGN_SYSTEM.md +168 -140
- package/symbols_mcp/skills/MIGRATE_TO_SYMBOLS.md +256 -236
- package/.claude/settings.local.json +0 -9
- package/generate-mcpb.sh +0 -17
- package/manifest.json +0 -67
- package/publish.sh +0 -51
- package/pyproject.toml +0 -19
- package/server.json +0 -32
- package/symbols-mcp.mcpb +0 -0
- package/symbols_mcp/__init__.py +0 -1
- package/symbols_mcp/server.py +0 -359
- package/uv.lock +0 -826
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
|
|
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
|
|
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
|
package/bin/symbols-mcp.js
CHANGED
|
@@ -1,17 +1,101 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
17
|
-
|
|
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.
|
|
4
|
-
"description": "MCP server for Symbols
|
|
3
|
+
"version": "1.0.8",
|
|
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": [
|
|
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
|
|
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:
|
|
11
|
+
export const Header = { extends: "Flex", padding: "A" };
|
|
12
12
|
|
|
13
13
|
// WRONG — never
|
|
14
|
-
export const Header = (el, state) => ({ padding:
|
|
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
|
|
40
|
-
export * from
|
|
39
|
+
export * from "./Navbar.js";
|
|
40
|
+
export * from "./PostCard.js";
|
|
41
41
|
|
|
42
42
|
// WRONG — this breaks everything
|
|
43
|
-
export * as Navbar from
|
|
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
|
|
64
|
-
export default {
|
|
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
|
|
100
|
-
}
|
|
99
|
+
const node = this.node; // 'this' is the DOMQL element
|
|
100
|
+
};
|
|
101
101
|
|
|
102
102
|
// In component
|
|
103
|
-
onClick: (e, el) => el.call(
|
|
104
|
-
onClick: (e, el) => el.call(
|
|
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 })
|
|
140
|
-
onClick: (e, el, s) => {
|
|
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 ✅
|
|
150
|
-
|
|
151
|
-
| `extends: 'X'`
|
|
152
|
-
| `childExtends: 'X'`
|
|
153
|
-
| `onClick: 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:
|
|
209
|
-
whiteSubtle:
|
|
210
|
-
whiteFaint:
|
|
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
|
-
{
|
|
216
|
+
{
|
|
217
|
+
color: "whiteMuted";
|
|
218
|
+
}
|
|
215
219
|
|
|
216
220
|
// WRONG — renders as literal text, not a style
|
|
217
|
-
{
|
|
218
|
-
|
|
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
|
|
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
|
|