@panagos/jojoscript 0.9.1 → 0.9.3
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 +26 -3
- package/package.json +1 -1
- package/src/pipeline-parser.js +8 -0
- package/src/runtime/index.js +5 -0
- package/src/runtime/mcp.js +93 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# JojoScript 0.9.
|
|
1
|
+
# JojoScript 0.9.3
|
|
2
2
|
|
|
3
3
|
JojoScript is a small JavaScript-compatible language and runtime for
|
|
4
4
|
expressive, composable pipelines. It adds lightweight syntax for declarations,
|
|
@@ -6,8 +6,8 @@ functions, pattern matching, type annotations, and pipelines while preserving
|
|
|
6
6
|
ordinary JavaScript. Its compiler uses a lightweight lexer and source
|
|
7
7
|
transformations rather than a full AST, keeping the toolchain compact. The
|
|
8
8
|
project also provides lazy and async collection operators, checkpointing,
|
|
9
|
-
retries, metrics, pipeline graphing, CLI workflows, effect handlers,
|
|
10
|
-
VS Code editor support.
|
|
9
|
+
retries, metrics, pipeline graphing, CLI workflows, effect handlers, MCP
|
|
10
|
+
support, and VS Code editor support.
|
|
11
11
|
|
|
12
12
|
See [CHANGELOG.md](CHANGELOG.md) for release history and
|
|
13
13
|
[example/](example/) for a complete runnable application that exercises
|
|
@@ -739,6 +739,29 @@ never through `|>`) and also uses pipeline syntax with overlapping names,
|
|
|
739
739
|
the compiler merges the auto-imported names into the existing import
|
|
740
740
|
instead of emitting a second, colliding import declaration.
|
|
741
741
|
|
|
742
|
+
### MCP support
|
|
743
|
+
|
|
744
|
+
JojoScript includes a small MCP-style JSON-RPC runtime for Node.js. It does
|
|
745
|
+
not add MCP-specific language syntax; tool implementations remain ordinary
|
|
746
|
+
JavaScript functions.
|
|
747
|
+
|
|
748
|
+
```js
|
|
749
|
+
import { createMcpServer, runMcpStdioServer } from "jojoscript/runtime"
|
|
750
|
+
|
|
751
|
+
const server = createMcpServer({
|
|
752
|
+
tools: {
|
|
753
|
+
echo: args => ({ content: [{ type: "text", text: args.value }] })
|
|
754
|
+
}
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
await runMcpStdioServer(server)
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
The server accepts `tools/call` requests and returns JSON-RPC responses on
|
|
761
|
+
standard output. It is intentionally minimal: supporting tool registration,
|
|
762
|
+
request dispatch, structured errors, and the stdin/stdout transport without any
|
|
763
|
+
external NPM packages.
|
|
764
|
+
|
|
742
765
|
## Example
|
|
743
766
|
|
|
744
767
|
See [example/](example/). It is a complete runnable project covering every
|
package/package.json
CHANGED
package/src/pipeline-parser.js
CHANGED
|
@@ -327,6 +327,14 @@ export function emitPipeline(pipeline, options = {}) {
|
|
|
327
327
|
continue
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
if (stage.name === 'pass') {
|
|
331
|
+
if (stage.args.length === 0) continue
|
|
332
|
+
if (stage.args.length === 1 && stage.args[0] === '_') {
|
|
333
|
+
continue
|
|
334
|
+
}
|
|
335
|
+
throw new SyntaxError('the `pass` pipeline stage takes no arguments or a single `_` placeholder')
|
|
336
|
+
}
|
|
337
|
+
|
|
330
338
|
if (stage.name.startsWith('.')) {
|
|
331
339
|
const args = stage.args.slice()
|
|
332
340
|
const placeholderIndex = args.indexOf('_')
|
package/src/runtime/index.js
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Minimal MCP-style JSON-RPC transport for Node.js. The server is protocol-
|
|
3
|
+
* focused rather than introducing MCP-specific JojoScript syntax; tool
|
|
4
|
+
* implementations remain ordinary JavaScript functions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createInterface } from 'node:readline'
|
|
8
|
+
|
|
9
|
+
export class McpServer {
|
|
10
|
+
constructor({ tools = {} } = {}) {
|
|
11
|
+
this.tools = new Map(Object.entries(tools))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async handleRequest(message) {
|
|
15
|
+
if (!message || typeof message !== 'object') {
|
|
16
|
+
return {
|
|
17
|
+
jsonrpc: '2.0',
|
|
18
|
+
id: null,
|
|
19
|
+
error: {
|
|
20
|
+
code: -32600,
|
|
21
|
+
message: 'Invalid Request'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (message.method !== 'tools/call') {
|
|
27
|
+
return {
|
|
28
|
+
jsonrpc: '2.0',
|
|
29
|
+
id: message.id ?? null,
|
|
30
|
+
error: {
|
|
31
|
+
code: -32601,
|
|
32
|
+
message: 'Method not found'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const toolName = message.params?.name
|
|
38
|
+
const toolArgs = message.params?.arguments ?? {}
|
|
39
|
+
const tool = this.tools.get(toolName)
|
|
40
|
+
|
|
41
|
+
if (!tool || typeof tool !== 'function') {
|
|
42
|
+
return {
|
|
43
|
+
jsonrpc: '2.0',
|
|
44
|
+
id: message.id ?? null,
|
|
45
|
+
error: {
|
|
46
|
+
code: -32602,
|
|
47
|
+
message: `Unknown tool: ${toolName}`
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const result = await tool(toolArgs)
|
|
54
|
+
return {
|
|
55
|
+
jsonrpc: '2.0',
|
|
56
|
+
id: message.id ?? null,
|
|
57
|
+
result
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
jsonrpc: '2.0',
|
|
62
|
+
id: message.id ?? null,
|
|
63
|
+
error: {
|
|
64
|
+
code: -32603,
|
|
65
|
+
message: error instanceof Error ? error.message : String(error)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createMcpServer(options = {}) {
|
|
73
|
+
return new McpServer(options)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function runMcpStdioServer(server, options = {}) {
|
|
77
|
+
const input = options.input ?? process.stdin
|
|
78
|
+
const output = options.output ?? process.stdout
|
|
79
|
+
|
|
80
|
+
const lines = Array.isArray(input)
|
|
81
|
+
? input.values()
|
|
82
|
+
: createInterface({ input, crlfDelay: Infinity })
|
|
83
|
+
|
|
84
|
+
for await (const line of lines) {
|
|
85
|
+
if (!line.trim()) continue
|
|
86
|
+
|
|
87
|
+
const message = JSON.parse(line)
|
|
88
|
+
const response = await server.handleRequest(message)
|
|
89
|
+
output.write(`${JSON.stringify(response)}\n`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return 0
|
|
93
|
+
}
|