@gesslar/fluffos-mcp 0.1.3 → 0.2.1
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/.claude/settings.local.json +7 -0
- package/.github/workflows/Quality.yaml +21 -0
- package/.github/workflows/Release.yaml +16 -0
- package/eslint.config.js +8 -164
- package/package.json +33 -26
- package/scripts/search_docs.sh +0 -0
- package/src/index.js +112 -124
- package/.github/workflows/ci.yml +0 -33
- package/index.js +0 -306
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Quality
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
schedule:
|
|
10
|
+
- cron: "20 14 * * 1"
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
Quality:
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
uses: gesslar/Maint/.github/workflows/Quality.yaml@main
|
|
17
|
+
secrets: inherit
|
|
18
|
+
with:
|
|
19
|
+
package_manager: "auto"
|
|
20
|
+
perform_linting: "${{ vars.PERFORM_LINTING || 'yes' }}"
|
|
21
|
+
perform_testing: "${{ vars.PERFORM_TESTING || 'no' }}"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
Release:
|
|
10
|
+
uses: gesslar/Maint/.github/workflows/Release.yaml@main
|
|
11
|
+
secrets: inherit
|
|
12
|
+
with:
|
|
13
|
+
package_manager: "auto"
|
|
14
|
+
quality_check: "Quality"
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
package/eslint.config.js
CHANGED
|
@@ -1,167 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import jsdoc from "eslint-plugin-jsdoc"
|
|
3
|
-
import stylistic from "@stylistic/eslint-plugin"
|
|
4
|
-
import globals from "globals"
|
|
1
|
+
import uglify from "@gesslar/uglier"
|
|
5
2
|
|
|
6
3
|
export default [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
ecmaVersion: "latest",
|
|
15
|
-
sourceType: "module",
|
|
16
|
-
globals: {
|
|
17
|
-
...globals.node,
|
|
18
|
-
fetch: "readonly",
|
|
19
|
-
Headers: "readonly",
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
// Add override for webview files to include browser globals
|
|
24
|
-
{
|
|
25
|
-
name: "gesslar/uglier/webview-env",
|
|
26
|
-
files: ["src/webview/**/*.{js,mjs,cjs}"],
|
|
27
|
-
languageOptions: {
|
|
28
|
-
globals: {
|
|
29
|
-
...globals.browser,
|
|
30
|
-
acquireVsCodeApi: "readonly"
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
// Add override for .cjs files to treat as CommonJS
|
|
35
|
-
{
|
|
36
|
-
name: "gesslar/uglier/cjs-override",
|
|
37
|
-
files: ["src/**/*.cjs"],
|
|
38
|
-
languageOptions: {
|
|
39
|
-
sourceType: "script",
|
|
40
|
-
ecmaVersion: 2021
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
// Add override for .mjs files to treat as ES modules
|
|
44
|
-
{
|
|
45
|
-
name: "gesslar/uglier/mjs-override",
|
|
46
|
-
files: ["src/**/*.mjs"],
|
|
47
|
-
languageOptions: {
|
|
48
|
-
sourceType: "module",
|
|
49
|
-
ecmaVersion: 2021
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: "gesslar/uglier/lints-js",
|
|
54
|
-
files: ["{work,src}/**/*.{mjs,cjs,js}"],
|
|
55
|
-
plugins: {
|
|
56
|
-
"@stylistic": stylistic,
|
|
57
|
-
},
|
|
58
|
-
rules: {
|
|
59
|
-
"@stylistic/arrow-parens": ["error", "as-needed"],
|
|
60
|
-
"@stylistic/arrow-spacing": ["error", { before: true, after: true }],
|
|
61
|
-
"@stylistic/brace-style": ["error", "1tbs", {allowSingleLine: false}],
|
|
62
|
-
"@stylistic/nonblock-statement-body-position": ["error", "below"],
|
|
63
|
-
"@stylistic/padding-line-between-statements": [
|
|
64
|
-
"error",
|
|
65
|
-
{blankLine: "always", prev: "if", next: "*"},
|
|
66
|
-
{blankLine: "always", prev: "*", next: "return"},
|
|
67
|
-
{blankLine: "always", prev: "while", next: "*"},
|
|
68
|
-
{blankLine: "always", prev: "for", next: "*"},
|
|
69
|
-
{blankLine: "always", prev: "switch", next: "*"},
|
|
70
|
-
{blankLine: "always", prev: "do", next: "*"},
|
|
71
|
-
// {blankLine: "always", prev: ["const", "let", "var"], next: "*"},
|
|
72
|
-
// {blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]},
|
|
73
|
-
{blankLine: "always", prev: "directive", next: "*" },
|
|
74
|
-
{blankLine: "any", prev: "directive", next: "directive" },
|
|
75
|
-
],
|
|
76
|
-
"@stylistic/eol-last": ["error", "always"],
|
|
77
|
-
"@stylistic/indent": ["error", 2, {
|
|
78
|
-
SwitchCase: 1 // Indents `case` statements one level deeper than `switch`
|
|
79
|
-
}],
|
|
80
|
-
"@stylistic/key-spacing": ["error", { beforeColon: false, afterColon: true }],
|
|
81
|
-
"@stylistic/keyword-spacing": ["error", {
|
|
82
|
-
before: false,
|
|
83
|
-
after: true,
|
|
84
|
-
overrides: {
|
|
85
|
-
// Control statements
|
|
86
|
-
return: { before: true, after: true },
|
|
87
|
-
if: { after: false },
|
|
88
|
-
else: { before: true, after: true },
|
|
89
|
-
for: { after: false },
|
|
90
|
-
while: { before: true, after: false },
|
|
91
|
-
do: { after: true },
|
|
92
|
-
switch: { after: false },
|
|
93
|
-
case: { before: true, after: true },
|
|
94
|
-
throw: { before: true, after: false } ,
|
|
95
|
-
|
|
96
|
-
// Keywords
|
|
97
|
-
as: { before: true, after: true },
|
|
98
|
-
of: { before: true, after: true },
|
|
99
|
-
from: { before: true, after: true },
|
|
100
|
-
async: { before: true, after: true },
|
|
101
|
-
await: { before: true, after: false },
|
|
102
|
-
class: { before: true, after: true },
|
|
103
|
-
const: { before: true, after: true },
|
|
104
|
-
let: { before: true, after: true },
|
|
105
|
-
var: { before: true, after: true },
|
|
106
|
-
|
|
107
|
-
// Exception handling
|
|
108
|
-
catch: { before: true, after: true },
|
|
109
|
-
finally: { before: true, after: true },
|
|
110
|
-
}
|
|
111
|
-
}],
|
|
112
|
-
// Blocks
|
|
113
|
-
"@stylistic/space-before-blocks": ["error", "always"],
|
|
114
|
-
"@stylistic/max-len": ["warn", {
|
|
115
|
-
code: 80,
|
|
116
|
-
ignoreComments: true,
|
|
117
|
-
ignoreUrls: true,
|
|
118
|
-
ignoreStrings: true,
|
|
119
|
-
ignoreTemplateLiterals: true,
|
|
120
|
-
ignoreRegExpLiterals: true,
|
|
121
|
-
tabWidth: 2
|
|
122
|
-
}],
|
|
123
|
-
"@stylistic/no-tabs": "error",
|
|
124
|
-
"@stylistic/no-trailing-spaces": ["error"],
|
|
125
|
-
"@stylistic/object-curly-spacing": ["error", "never", {
|
|
126
|
-
objectsInObjects: false,
|
|
127
|
-
arraysInObjects: false
|
|
128
|
-
}],
|
|
129
|
-
"@stylistic/quotes": ["error", "double", {
|
|
130
|
-
avoidEscape: true,
|
|
131
|
-
allowTemplateLiterals: "always"
|
|
132
|
-
}],
|
|
133
|
-
"@stylistic/semi": ["error", "never"],
|
|
134
|
-
"@stylistic/space-before-function-paren": ["error", "never"],
|
|
135
|
-
"@stylistic/yield-star-spacing": ["error", { before: true, after: false }],
|
|
136
|
-
"constructor-super": "error",
|
|
137
|
-
"no-unexpected-multiline": "error",
|
|
138
|
-
"no-unused-vars": ["error", {
|
|
139
|
-
caughtErrors: "all",
|
|
140
|
-
caughtErrorsIgnorePattern: "^_+",
|
|
141
|
-
argsIgnorePattern: "^_+",
|
|
142
|
-
destructuredArrayIgnorePattern: "^_+",
|
|
143
|
-
varsIgnorePattern: "^_+"
|
|
144
|
-
}],
|
|
145
|
-
"no-useless-assignment": "error",
|
|
146
|
-
"prefer-const": "error",
|
|
147
|
-
"@stylistic/no-multiple-empty-lines": ["error", { max: 1 }],
|
|
148
|
-
"@stylistic/array-bracket-spacing": ["error", "never"],
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
name: "gesslar/uglier/lints-jsdoc",
|
|
153
|
-
files: ["{work,src}/**/*.{mjs,cjs,js}"],
|
|
154
|
-
plugins: {
|
|
155
|
-
jsdoc,
|
|
156
|
-
},
|
|
157
|
-
rules: {
|
|
158
|
-
"jsdoc/require-description": "error",
|
|
159
|
-
"jsdoc/tag-lines": ["error", "any", {"startLines":1}],
|
|
160
|
-
"jsdoc/require-jsdoc": ["error", { publicOnly: true }],
|
|
161
|
-
"jsdoc/check-tag-names": "error",
|
|
162
|
-
"jsdoc/check-types": "error",
|
|
163
|
-
"jsdoc/require-param-type": "error",
|
|
164
|
-
"jsdoc/require-returns-type": "error"
|
|
165
|
-
}
|
|
166
|
-
}
|
|
4
|
+
...uglify({
|
|
5
|
+
with: [
|
|
6
|
+
"lints-js", // default files: ["**/*.{js,mjs,cjs}"]
|
|
7
|
+
"lints-jsdoc", // default files: ["**/*.{js,mjs,cjs}"]
|
|
8
|
+
"node", // default files: ["**/*.{js,mjs,cjs}"]
|
|
9
|
+
]
|
|
10
|
+
})
|
|
167
11
|
]
|
package/package.json
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gesslar/fluffos-mcp",
|
|
3
|
-
"version": "0.1.3",
|
|
4
3
|
"description": "MCP server for FluffOS driver tools - validate and disassemble LPC code",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"fluffos-mcp": "./src/index.js"
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "gesslar",
|
|
6
|
+
"url": "https://gesslar.dev"
|
|
9
7
|
},
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"pr": "gt submit --publish --restack --ai -m",
|
|
17
|
-
"patch": "npm version patch",
|
|
18
|
-
"minor": "npm version minor",
|
|
19
|
-
"major": "npm version major"
|
|
8
|
+
"version": "0.2.1",
|
|
9
|
+
"license": "Unlicense",
|
|
10
|
+
"homepage": "https://github.com/gesslar/fluffos-mcp#readme",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/gesslar/fluffos-mcp.git"
|
|
20
14
|
},
|
|
21
15
|
"keywords": [
|
|
22
16
|
"fluffos",
|
|
@@ -29,19 +23,32 @@
|
|
|
29
23
|
"white",
|
|
30
24
|
"cheddar"
|
|
31
25
|
],
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22"
|
|
28
|
+
},
|
|
29
|
+
"main": "src/index.js",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"bin": {
|
|
32
|
+
"fluffos-mcp": "./src/index.js"
|
|
37
33
|
},
|
|
38
34
|
"dependencies": {
|
|
39
|
-
"@
|
|
35
|
+
"@gesslar/toolkit": "^3.23.0",
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
37
|
+
"zod": "^4.3.5"
|
|
40
38
|
},
|
|
41
39
|
"devDependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
"@gesslar/uglier": "^1.1.0",
|
|
41
|
+
"eslint": "^9.39.2"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"start": "node src/index.js",
|
|
45
|
+
"lint": "eslint src/",
|
|
46
|
+
"lint:fix": "eslint src/ --fix",
|
|
47
|
+
"submit": "pnpm publish --access public --//registry.npmjs.org/:_authToken=\"${NPM_ACCESS_TOKEN}\"",
|
|
48
|
+
"update": "pnpm self-update && pnpx npm-check-updates -u && pnpm install",
|
|
49
|
+
"pr": "gt submit -p --ai",
|
|
50
|
+
"patch": "pnpm version patch",
|
|
51
|
+
"minor": "pnpm version minor",
|
|
52
|
+
"major": "pnpm version major"
|
|
46
53
|
}
|
|
47
|
-
}
|
|
54
|
+
}
|
package/scripts/search_docs.sh
CHANGED
|
File without changes
|
package/src/index.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js"
|
|
4
4
|
import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
5
|
-
import
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
8
|
-
} from "@modelcontextprotocol/sdk/types.js"
|
|
5
|
+
import * as z from "zod/v4"
|
|
9
6
|
import {spawn} from "child_process"
|
|
10
|
-
import
|
|
11
|
-
import fs from "fs"
|
|
7
|
+
import {FileObject, DirectoryObject} from "@gesslar/toolkit"
|
|
12
8
|
|
|
13
9
|
class FluffOSMCPServer {
|
|
14
10
|
constructor() {
|
|
15
|
-
this.server = new
|
|
11
|
+
this.server = new McpServer(
|
|
16
12
|
{
|
|
17
13
|
name: "fluffos-mcp-server",
|
|
18
14
|
version: "0.1.0",
|
|
@@ -28,7 +24,9 @@ class FluffOSMCPServer {
|
|
|
28
24
|
this.configFile = process.env.MUD_RUNTIME_CONFIG_FILE
|
|
29
25
|
this.docsDir = process.env.FLUFFOS_DOCS_DIR
|
|
30
26
|
this.mudlibDir = null
|
|
27
|
+
}
|
|
31
28
|
|
|
29
|
+
async initialize() {
|
|
32
30
|
if(!this.binDir) {
|
|
33
31
|
console.error("Error: FLUFFOS_BIN_DIR environment variable not set")
|
|
34
32
|
process.exit(1)
|
|
@@ -40,28 +38,29 @@ class FluffOSMCPServer {
|
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
// Parse mudlib directory from config file
|
|
43
|
-
this.mudlibDir = this.parseMudlibDir()
|
|
41
|
+
this.mudlibDir = await this.parseMudlibDir()
|
|
44
42
|
|
|
45
43
|
console.error(`FluffOS bin directory: ${this.binDir}`)
|
|
46
44
|
console.error(`FluffOS config file: ${this.configFile}`)
|
|
47
45
|
console.error(`Mudlib directory: ${this.mudlibDir || "(not found in config)"}`)
|
|
48
46
|
|
|
49
|
-
if(this.docsDir)
|
|
47
|
+
if(this.docsDir)
|
|
50
48
|
console.error(`FluffOS docs directory: ${this.docsDir}`)
|
|
51
|
-
|
|
49
|
+
else
|
|
52
50
|
console.error(`FluffOS docs directory: not set (doc lookup disabled)`)
|
|
53
|
-
}
|
|
54
51
|
|
|
55
|
-
this.
|
|
52
|
+
this.setupTools()
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
parseMudlibDir() {
|
|
55
|
+
async parseMudlibDir() {
|
|
59
56
|
try {
|
|
60
|
-
const
|
|
57
|
+
const configFile = new FileObject(this.configFile)
|
|
58
|
+
const configContent = await configFile.read()
|
|
61
59
|
const match = configContent.match(/^mudlib directory\s*:\s*(.+)$/m)
|
|
62
|
-
|
|
60
|
+
|
|
61
|
+
if(match)
|
|
63
62
|
return match[1].trim()
|
|
64
|
-
|
|
63
|
+
|
|
65
64
|
} catch(err) {
|
|
66
65
|
console.error(`Warning: Could not parse mudlib directory from config: ${err.message}`)
|
|
67
66
|
}
|
|
@@ -73,7 +72,7 @@ class FluffOSMCPServer {
|
|
|
73
72
|
// If we have a mudlib directory and the file path is absolute and starts with mudlib dir,
|
|
74
73
|
// convert it to a relative path
|
|
75
74
|
if(this.mudlibDir &&
|
|
76
|
-
|
|
75
|
+
lpcFile.startsWith("/") &&
|
|
77
76
|
lpcFile.startsWith(this.mudlibDir)
|
|
78
77
|
) {
|
|
79
78
|
// Remove mudlib directory prefix and leading slash
|
|
@@ -84,109 +83,57 @@ class FluffOSMCPServer {
|
|
|
84
83
|
return lpcFile
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
name: "fluffos_disassemble",
|
|
109
|
-
description:
|
|
110
|
-
"Disassemble an LPC file to show compiled bytecode using lpcc. Returns detailed bytecode, function tables, strings, and disassembly. Useful for debugging and understanding how code compiles.",
|
|
111
|
-
inputSchema: {
|
|
112
|
-
type: "object",
|
|
113
|
-
properties: {
|
|
114
|
-
file: {
|
|
115
|
-
type: "string",
|
|
116
|
-
description: "Absolute path to the LPC file to disassemble",
|
|
117
|
-
},
|
|
86
|
+
setupTools() {
|
|
87
|
+
// Register validate tool
|
|
88
|
+
this.server.registerTool("fluffos_validate", {
|
|
89
|
+
description: "Validate an LPC file using the FluffOS driver's symbol tool. " +
|
|
90
|
+
"Compiles the file and reports success or failure with any " +
|
|
91
|
+
"compilation errors. Fast and lightweight check for code validity.",
|
|
92
|
+
inputSchema: z.object({
|
|
93
|
+
file: z.string().describe("Absolute path to the LPC file to validate"),
|
|
94
|
+
}),
|
|
95
|
+
}, async({file}) => {
|
|
96
|
+
try {
|
|
97
|
+
const result = await this.runSymbol(file)
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: result,
|
|
118
104
|
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
type: "object",
|
|
128
|
-
properties: {
|
|
129
|
-
query: {
|
|
130
|
-
type: "string",
|
|
131
|
-
description: "Term to search for in documentation (e.g., 'call_out', 'mapping', 'socket')",
|
|
132
|
-
},
|
|
105
|
+
],
|
|
106
|
+
}
|
|
107
|
+
} catch(error) {
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: "text",
|
|
112
|
+
text: `Error: ${error.message}`,
|
|
133
113
|
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
this.server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
141
|
-
const {name, arguments: args} = request.params
|
|
114
|
+
],
|
|
115
|
+
isError: true,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
142
119
|
|
|
120
|
+
// Register disassemble tool
|
|
121
|
+
this.server.registerTool("fluffos_disassemble", {
|
|
122
|
+
description: "Disassemble an LPC file to show compiled bytecode using lpcc. Returns detailed bytecode, function tables, strings, and disassembly. Useful for debugging and understanding how code compiles.",
|
|
123
|
+
inputSchema: z.object({
|
|
124
|
+
file: z.string().describe("Absolute path to the LPC file to disassemble"),
|
|
125
|
+
}),
|
|
126
|
+
}, async({file}) => {
|
|
143
127
|
try {
|
|
144
|
-
|
|
145
|
-
case "fluffos_validate": {
|
|
146
|
-
const result = await this.runSymbol(args.file)
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
content: [
|
|
150
|
-
{
|
|
151
|
-
type: "text",
|
|
152
|
-
text: result,
|
|
153
|
-
},
|
|
154
|
-
],
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
case "fluffos_disassemble": {
|
|
159
|
-
const result = await this.runLpcc(args.file)
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
content: [
|
|
163
|
-
{
|
|
164
|
-
type: "text",
|
|
165
|
-
text: result,
|
|
166
|
-
},
|
|
167
|
-
],
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
case "fluffos_doc_lookup": {
|
|
172
|
-
if(!this.docsDir) {
|
|
173
|
-
throw new Error("Documentation lookup is not available (FLUFFOS_DOCS_DIR not set)")
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const result = await this.searchDocs(args.query)
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
content: [
|
|
180
|
-
{
|
|
181
|
-
type: "text",
|
|
182
|
-
text: result,
|
|
183
|
-
},
|
|
184
|
-
],
|
|
185
|
-
}
|
|
186
|
-
}
|
|
128
|
+
const result = await this.runLpcc(file)
|
|
187
129
|
|
|
188
|
-
|
|
189
|
-
|
|
130
|
+
return {
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: "text",
|
|
134
|
+
text: result,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
190
137
|
}
|
|
191
138
|
} catch(error) {
|
|
192
139
|
return {
|
|
@@ -200,14 +147,49 @@ class FluffOSMCPServer {
|
|
|
200
147
|
}
|
|
201
148
|
}
|
|
202
149
|
})
|
|
150
|
+
|
|
151
|
+
// Register doc lookup tool (conditional)
|
|
152
|
+
if(this.docsDir) {
|
|
153
|
+
this.server.registerTool("fluffos_doc_lookup", {
|
|
154
|
+
description: "Search FluffOS documentation for information about efuns, applies, concepts, etc. Searches markdown documentation files.",
|
|
155
|
+
inputSchema: z.object({
|
|
156
|
+
query: z.string().describe("Term to search for in documentation (e.g., 'call_out', 'mapping', 'socket')"),
|
|
157
|
+
}),
|
|
158
|
+
}, async({query}) => {
|
|
159
|
+
try {
|
|
160
|
+
const result = await this.searchDocs(query)
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
content: [
|
|
164
|
+
{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: result,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
}
|
|
170
|
+
} catch(error) {
|
|
171
|
+
return {
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: `Error: ${error.message}`,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
isError: true,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
}
|
|
203
183
|
}
|
|
204
184
|
|
|
205
185
|
async runSymbol(lpcFile) {
|
|
206
186
|
return new Promise((resolve, reject) => {
|
|
207
187
|
const normalizedPath = this.normalizePath(lpcFile)
|
|
208
|
-
const
|
|
188
|
+
const binDir = new DirectoryObject(this.binDir)
|
|
189
|
+
const symbolPath = binDir.getFile("symbol").path
|
|
190
|
+
const configFile = new FileObject(this.configFile)
|
|
209
191
|
const proc = spawn(symbolPath, [this.configFile, normalizedPath], {
|
|
210
|
-
cwd:
|
|
192
|
+
cwd: configFile.parentPath,
|
|
211
193
|
})
|
|
212
194
|
|
|
213
195
|
let stdout = ""
|
|
@@ -224,11 +206,11 @@ class FluffOSMCPServer {
|
|
|
224
206
|
proc.on("close", code => {
|
|
225
207
|
const output = (stdout + stderr).trim()
|
|
226
208
|
|
|
227
|
-
if(code === 0)
|
|
209
|
+
if(code === 0)
|
|
228
210
|
resolve(`✓ File validated successfully\n\n${output}`)
|
|
229
|
-
|
|
211
|
+
else
|
|
230
212
|
resolve(`✗ Validation failed (exit code: ${code})\n\n${output}`)
|
|
231
|
-
|
|
213
|
+
|
|
232
214
|
})
|
|
233
215
|
|
|
234
216
|
proc.on("error", err => {
|
|
@@ -240,9 +222,11 @@ class FluffOSMCPServer {
|
|
|
240
222
|
async runLpcc(lpcFile) {
|
|
241
223
|
return new Promise((resolve, reject) => {
|
|
242
224
|
const normalizedPath = this.normalizePath(lpcFile)
|
|
243
|
-
const
|
|
225
|
+
const binDir = new DirectoryObject(this.binDir)
|
|
226
|
+
const lpccPath = binDir.getFile("lpcc").path
|
|
227
|
+
const configFile = new FileObject(this.configFile)
|
|
244
228
|
const proc = spawn(lpccPath, [this.configFile, normalizedPath], {
|
|
245
|
-
cwd:
|
|
229
|
+
cwd: configFile.parentPath,
|
|
246
230
|
})
|
|
247
231
|
|
|
248
232
|
let stdout = ""
|
|
@@ -274,7 +258,9 @@ class FluffOSMCPServer {
|
|
|
274
258
|
|
|
275
259
|
async searchDocs(query) {
|
|
276
260
|
return new Promise((resolve, reject) => {
|
|
277
|
-
const
|
|
261
|
+
const moduleFile = new FileObject(new URL(import.meta.url).pathname)
|
|
262
|
+
const scriptsDir = moduleFile.parent.getDirectory("scripts")
|
|
263
|
+
const scriptPath = scriptsDir.getFile("search_docs.sh").path
|
|
278
264
|
const proc = spawn(scriptPath, [this.docsDir, query])
|
|
279
265
|
|
|
280
266
|
let stdout = ""
|
|
@@ -307,6 +293,8 @@ class FluffOSMCPServer {
|
|
|
307
293
|
}
|
|
308
294
|
|
|
309
295
|
async run() {
|
|
296
|
+
await this.initialize()
|
|
297
|
+
|
|
310
298
|
const transport = new StdioServerTransport()
|
|
311
299
|
await this.server.connect(transport)
|
|
312
300
|
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
name: Giddyup
|
|
2
|
-
permissions:
|
|
3
|
-
contents: read
|
|
4
|
-
|
|
5
|
-
on:
|
|
6
|
-
push:
|
|
7
|
-
branches: [main]
|
|
8
|
-
pull_request:
|
|
9
|
-
branches: [main]
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
cowyboysounds:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
|
|
15
|
-
strategy:
|
|
16
|
-
matrix:
|
|
17
|
-
node-version: [20.x, 22.x]
|
|
18
|
-
|
|
19
|
-
steps:
|
|
20
|
-
- name: Checkout code
|
|
21
|
-
uses: actions/checkout@v4
|
|
22
|
-
|
|
23
|
-
- name: Setup Node.js ${{ matrix.node-version }}
|
|
24
|
-
uses: actions/setup-node@v4
|
|
25
|
-
with:
|
|
26
|
-
node-version: ${{ matrix.node-version }}
|
|
27
|
-
cache: "npm"
|
|
28
|
-
|
|
29
|
-
- name: Install dependencies
|
|
30
|
-
run: npm ci
|
|
31
|
-
|
|
32
|
-
- name: Run ESLint
|
|
33
|
-
run: npm run lint
|
package/index.js
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
8
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import { spawn } from "child_process";
|
|
10
|
-
import path from "path";
|
|
11
|
-
import fs from "fs";
|
|
12
|
-
|
|
13
|
-
class FluffOSMCPServer {
|
|
14
|
-
constructor() {
|
|
15
|
-
this.server = new Server(
|
|
16
|
-
{
|
|
17
|
-
name: "fluffos-mcp-server",
|
|
18
|
-
version: "0.1.0",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
capabilities: {
|
|
22
|
-
tools: {},
|
|
23
|
-
},
|
|
24
|
-
}
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
this.binDir = process.env.FLUFFOS_BIN_DIR;
|
|
28
|
-
this.configFile = process.env.MUD_RUNTIME_CONFIG_FILE;
|
|
29
|
-
this.docsDir = process.env.FLUFFOS_DOCS_DIR;
|
|
30
|
-
|
|
31
|
-
if (!this.binDir) {
|
|
32
|
-
console.error("Error: FLUFFOS_BIN_DIR environment variable not set");
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!this.configFile) {
|
|
37
|
-
console.error("Error: MUD_RUNTIME_CONFIG_FILE environment variable not set");
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Parse mudlib directory from config file
|
|
42
|
-
this.mudlibDir = this.parseMudlibDir();
|
|
43
|
-
|
|
44
|
-
console.error(`FluffOS bin directory: ${this.binDir}`);
|
|
45
|
-
console.error(`FluffOS config file: ${this.configFile}`);
|
|
46
|
-
console.error(`Mudlib directory: ${this.mudlibDir || "(not found in config)"}`);
|
|
47
|
-
|
|
48
|
-
if (this.docsDir) {
|
|
49
|
-
console.error(`FluffOS docs directory: ${this.docsDir}`);
|
|
50
|
-
} else {
|
|
51
|
-
console.error(`FluffOS docs directory: not set (doc lookup disabled)`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
this.setupHandlers();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
parseMudlibDir() {
|
|
58
|
-
try {
|
|
59
|
-
const configContent = fs.readFileSync(this.configFile, "utf8");
|
|
60
|
-
const match = configContent.match(/^mudlib directory\s*:\s*(.+)$/m);
|
|
61
|
-
if (match) {
|
|
62
|
-
return match[1].trim();
|
|
63
|
-
}
|
|
64
|
-
} catch (err) {
|
|
65
|
-
console.error(`Warning: Could not parse mudlib directory from config: ${err.message}`);
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
normalizePath(lpcFile) {
|
|
71
|
-
// If we have a mudlib directory and the file path is absolute and starts with mudlib dir,
|
|
72
|
-
// convert it to a relative path
|
|
73
|
-
if (this.mudlibDir && path.isAbsolute(lpcFile) && lpcFile.startsWith(this.mudlibDir)) {
|
|
74
|
-
// Remove mudlib directory prefix and leading slash
|
|
75
|
-
return lpcFile.substring(this.mudlibDir.length).replace(/^\/+/, "");
|
|
76
|
-
}
|
|
77
|
-
// Otherwise return as-is (already relative or not under mudlib)
|
|
78
|
-
return lpcFile;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
setupHandlers() {
|
|
82
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
83
|
-
tools: [
|
|
84
|
-
{
|
|
85
|
-
name: "fluffos_validate",
|
|
86
|
-
description:
|
|
87
|
-
"Validate an LPC file using the FluffOS driver's symbol tool. Compiles the file and reports success or failure with any compilation errors. Fast and lightweight check for code validity.",
|
|
88
|
-
inputSchema: {
|
|
89
|
-
type: "object",
|
|
90
|
-
properties: {
|
|
91
|
-
file: {
|
|
92
|
-
type: "string",
|
|
93
|
-
description: "Absolute path to the LPC file to validate",
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
required: ["file"],
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: "fluffos_disassemble",
|
|
101
|
-
description:
|
|
102
|
-
"Disassemble an LPC file to show compiled bytecode using lpcc. Returns detailed bytecode, function tables, strings, and disassembly. Useful for debugging and understanding how code compiles.",
|
|
103
|
-
inputSchema: {
|
|
104
|
-
type: "object",
|
|
105
|
-
properties: {
|
|
106
|
-
file: {
|
|
107
|
-
type: "string",
|
|
108
|
-
description: "Absolute path to the LPC file to disassemble",
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
required: ["file"],
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
...(this.docsDir ? [{
|
|
115
|
-
name: "fluffos_doc_lookup",
|
|
116
|
-
description:
|
|
117
|
-
"Search FluffOS documentation for information about efuns, applies, concepts, etc. Searches markdown documentation files.",
|
|
118
|
-
inputSchema: {
|
|
119
|
-
type: "object",
|
|
120
|
-
properties: {
|
|
121
|
-
query: {
|
|
122
|
-
type: "string",
|
|
123
|
-
description: "Term to search for in documentation (e.g., 'call_out', 'mapping', 'socket')",
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
required: ["query"],
|
|
127
|
-
},
|
|
128
|
-
}] : []),
|
|
129
|
-
],
|
|
130
|
-
}));
|
|
131
|
-
|
|
132
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
133
|
-
const { name, arguments: args } = request.params;
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
switch (name) {
|
|
137
|
-
case "fluffos_validate": {
|
|
138
|
-
const result = await this.runSymbol(args.file);
|
|
139
|
-
return {
|
|
140
|
-
content: [
|
|
141
|
-
{
|
|
142
|
-
type: "text",
|
|
143
|
-
text: result,
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
case "fluffos_disassemble": {
|
|
150
|
-
const result = await this.runLpcc(args.file);
|
|
151
|
-
return {
|
|
152
|
-
content: [
|
|
153
|
-
{
|
|
154
|
-
type: "text",
|
|
155
|
-
text: result,
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
case "fluffos_doc_lookup": {
|
|
162
|
-
if (!this.docsDir) {
|
|
163
|
-
throw new Error("Documentation lookup is not available (FLUFFOS_DOCS_DIR not set)");
|
|
164
|
-
}
|
|
165
|
-
const result = await this.searchDocs(args.query);
|
|
166
|
-
return {
|
|
167
|
-
content: [
|
|
168
|
-
{
|
|
169
|
-
type: "text",
|
|
170
|
-
text: result,
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
default:
|
|
177
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
178
|
-
}
|
|
179
|
-
} catch (error) {
|
|
180
|
-
return {
|
|
181
|
-
content: [
|
|
182
|
-
{
|
|
183
|
-
type: "text",
|
|
184
|
-
text: `Error: ${error.message}`,
|
|
185
|
-
},
|
|
186
|
-
],
|
|
187
|
-
isError: true,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async runSymbol(lpcFile) {
|
|
194
|
-
return new Promise((resolve, reject) => {
|
|
195
|
-
const normalizedPath = this.normalizePath(lpcFile);
|
|
196
|
-
const symbolPath = path.join(this.binDir, "symbol");
|
|
197
|
-
const proc = spawn(symbolPath, [this.configFile, normalizedPath], {
|
|
198
|
-
cwd: path.dirname(this.configFile),
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
let stdout = "";
|
|
202
|
-
let stderr = "";
|
|
203
|
-
|
|
204
|
-
proc.stdout.on("data", (data) => {
|
|
205
|
-
stdout += data.toString();
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
proc.stderr.on("data", (data) => {
|
|
209
|
-
stderr += data.toString();
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
proc.on("close", (code) => {
|
|
213
|
-
const output = (stdout + stderr).trim();
|
|
214
|
-
|
|
215
|
-
if (code === 0) {
|
|
216
|
-
resolve(`✓ File validated successfully\n\n${output}`);
|
|
217
|
-
} else {
|
|
218
|
-
resolve(`✗ Validation failed (exit code: ${code})\n\n${output}`);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
proc.on("error", (err) => {
|
|
223
|
-
reject(new Error(`Failed to run symbol: ${err.message}`));
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async runLpcc(lpcFile) {
|
|
229
|
-
return new Promise((resolve, reject) => {
|
|
230
|
-
const normalizedPath = this.normalizePath(lpcFile);
|
|
231
|
-
const lpccPath = path.join(this.binDir, "lpcc");
|
|
232
|
-
const proc = spawn(lpccPath, [this.configFile, normalizedPath], {
|
|
233
|
-
cwd: path.dirname(this.configFile),
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
let stdout = "";
|
|
237
|
-
let stderr = "";
|
|
238
|
-
|
|
239
|
-
proc.stdout.on("data", (data) => {
|
|
240
|
-
stdout += data.toString();
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
proc.stderr.on("data", (data) => {
|
|
244
|
-
stderr += data.toString();
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
proc.on("close", (code) => {
|
|
248
|
-
const output = (stdout + stderr).trim();
|
|
249
|
-
|
|
250
|
-
if (code === 0) {
|
|
251
|
-
resolve(output);
|
|
252
|
-
} else {
|
|
253
|
-
resolve(`Error (exit code: ${code}):\n\n${output}`);
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
proc.on("error", (err) => {
|
|
258
|
-
reject(new Error(`Failed to run lpcc: ${err.message}`));
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async searchDocs(query) {
|
|
264
|
-
return new Promise((resolve, reject) => {
|
|
265
|
-
const scriptPath = path.join(path.dirname(new URL(import.meta.url).pathname), "scripts", "search_docs.sh");
|
|
266
|
-
const proc = spawn(scriptPath, [this.docsDir, query]);
|
|
267
|
-
|
|
268
|
-
let stdout = "";
|
|
269
|
-
let stderr = "";
|
|
270
|
-
|
|
271
|
-
proc.stdout.on("data", (data) => {
|
|
272
|
-
stdout += data.toString();
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
proc.stderr.on("data", (data) => {
|
|
276
|
-
stderr += data.toString();
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
proc.on("close", (code) => {
|
|
280
|
-
if (code === 0) {
|
|
281
|
-
if (stdout.trim()) {
|
|
282
|
-
resolve(`Found documentation for "${query}":\n\n${stdout}`);
|
|
283
|
-
} else {
|
|
284
|
-
resolve(`No documentation found for "${query}".`);
|
|
285
|
-
}
|
|
286
|
-
} else {
|
|
287
|
-
resolve(`Error searching documentation:\n${stderr || stdout}`);
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
proc.on("error", (err) => {
|
|
292
|
-
reject(new Error(`Failed to search docs: ${err.message}`));
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async run() {
|
|
298
|
-
const transport = new StdioServerTransport();
|
|
299
|
-
await this.server.connect(transport);
|
|
300
|
-
|
|
301
|
-
console.error("FluffOS MCP Server running on stdio");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const server = new FluffOSMCPServer();
|
|
306
|
-
server.run().catch(console.error);
|