mikser-io-mcp 0.2.0 → 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/index.js +8 -7
- package/package.json +5 -1
- package/scripts/chatgpt.js +105 -0
- package/scripts/claude.js +121 -0
- package/scripts/index.js +57 -0
package/index.js
CHANGED
|
@@ -655,9 +655,11 @@ function pinoLevelNumber(name) {
|
|
|
655
655
|
// endpoints: { ... } // same shape as the previous mcp.endpoints
|
|
656
656
|
// }
|
|
657
657
|
//
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
//
|
|
658
|
+
// Presence in the plugins array IS the activation signal. The `mcp`
|
|
659
|
+
// config block is for tuning (path, endpoints, renderTimeout). When
|
|
660
|
+
// absent, defaults apply. For conditional activation, toggle the
|
|
661
|
+
// plugins array — `...(process.env.MCP ? ['mcp'] : [])` — same shape
|
|
662
|
+
// other plugins use.
|
|
661
663
|
export default (core) => {
|
|
662
664
|
// Use the runtime singleton imported above for substrate-internal
|
|
663
665
|
// code paths (createMcpSubstrate etc. reference it directly via
|
|
@@ -665,10 +667,9 @@ export default (core) => {
|
|
|
665
667
|
// they're both mikser-io's exported runtime singleton.
|
|
666
668
|
const { onLoaded, useLogger } = core
|
|
667
669
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
670
|
+
// Default the config block so every downstream `runtime.config.mcp.X`
|
|
671
|
+
// reference Just Works without optional-chaining everywhere.
|
|
672
|
+
runtime.config.mcp = runtime.config.mcp ?? {}
|
|
672
673
|
|
|
673
674
|
// Contribute MCP transport headers to engine's CORS arrays so
|
|
674
675
|
// browser-side MCP clients (basic-host, mcp-ui, etc.) can read
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mikser-io-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "MCP (Model Context Protocol) substrate and tools for mikser-io. Extracted from core to iterate on its own release cadence.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mikser-io-mcp": "./scripts/index.js"
|
|
9
|
+
},
|
|
7
10
|
"files": [
|
|
8
11
|
"index.js",
|
|
9
12
|
"preview.js",
|
|
10
13
|
"public",
|
|
14
|
+
"scripts",
|
|
11
15
|
"README.md",
|
|
12
16
|
"LICENSE"
|
|
13
17
|
],
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// register chatgpt — print copy-paste instructions for adding mikser
|
|
2
|
+
// to ChatGPT Desktop as a custom MCP connector.
|
|
3
|
+
//
|
|
4
|
+
// ChatGPT does NOT use a local config file. Connectors live in the
|
|
5
|
+
// user's OpenAI account, configured through the UI. OpenAI's servers
|
|
6
|
+
// connect to the MCP endpoint directly, so localhost won't work — a
|
|
7
|
+
// publicly-reachable HTTPS URL is mandatory.
|
|
8
|
+
//
|
|
9
|
+
// This script can't drop a file. The most it can do is render the
|
|
10
|
+
// exact name / description / URL the user pastes into the UI.
|
|
11
|
+
|
|
12
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
13
|
+
import { resolve } from 'node:path'
|
|
14
|
+
|
|
15
|
+
function parseArgs(args) {
|
|
16
|
+
const opts = {}
|
|
17
|
+
for (const a of args) {
|
|
18
|
+
if (a.startsWith('--url=')) opts.url = a.slice(6)
|
|
19
|
+
else if (a === '--url') opts._expectUrl = true
|
|
20
|
+
else if (opts._expectUrl) { opts.url = a; opts._expectUrl = false }
|
|
21
|
+
}
|
|
22
|
+
return opts
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readProjectMeta() {
|
|
26
|
+
const pkgPath = resolve(process.cwd(), 'package.json')
|
|
27
|
+
if (!existsSync(pkgPath)) {
|
|
28
|
+
throw new Error(`No package.json in ${process.cwd()} — run this from inside a mikser project directory.`)
|
|
29
|
+
}
|
|
30
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
31
|
+
if (!pkg.name) throw new Error('package.json has no "name" field — set one before registering.')
|
|
32
|
+
return {
|
|
33
|
+
name: pkg.name,
|
|
34
|
+
description: pkg.description ?? `MCP connector for ${pkg.name}.`,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function runChatGPT(args) {
|
|
39
|
+
const opts = parseArgs(args)
|
|
40
|
+
|
|
41
|
+
if (!opts.url) {
|
|
42
|
+
process.stderr.write(
|
|
43
|
+
`! ChatGPT needs a publicly-reachable HTTPS URL.
|
|
44
|
+
|
|
45
|
+
OpenAI's servers connect to your MCP endpoint directly — there's no stdio
|
|
46
|
+
bridge, no localhost. You'll need to expose mikser via a tunnel first:
|
|
47
|
+
|
|
48
|
+
ngrok http 3001
|
|
49
|
+
# or:
|
|
50
|
+
cloudflared tunnel --url http://localhost:3001
|
|
51
|
+
|
|
52
|
+
Then re-run with the public URL:
|
|
53
|
+
|
|
54
|
+
npx mikser-io-mcp register chatgpt --url https://YOUR-TUNNEL.example/mcp
|
|
55
|
+
|
|
56
|
+
ChatGPT's Developer mode + custom MCP connectors are currently in BETA
|
|
57
|
+
and require a Plus / Pro / Business / Enterprise / Edu account. See
|
|
58
|
+
https://help.openai.com/en/articles/12584461 for the current state.
|
|
59
|
+
`
|
|
60
|
+
)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { name, description } = readProjectMeta()
|
|
65
|
+
|
|
66
|
+
process.stdout.write(
|
|
67
|
+
`✓ Connector details for "${name}"
|
|
68
|
+
|
|
69
|
+
ChatGPT doesn't have a local config file — connectors are added through
|
|
70
|
+
the UI. Copy the three fields below.
|
|
71
|
+
|
|
72
|
+
Open ChatGPT (Desktop or web) and navigate to:
|
|
73
|
+
Settings → Connectors → Advanced → Developer mode (toggle ON)
|
|
74
|
+
Click + Create
|
|
75
|
+
|
|
76
|
+
Paste these into the form:
|
|
77
|
+
|
|
78
|
+
Name: ${name}
|
|
79
|
+
Description: ${description}
|
|
80
|
+
MCP Server URL: ${opts.url}
|
|
81
|
+
Authentication: None (or your scheme of choice for production)
|
|
82
|
+
|
|
83
|
+
Click Create. Then to use it:
|
|
84
|
+
|
|
85
|
+
1. Start a NEW chat (saved connectors are off-by-default per chat)
|
|
86
|
+
2. Open the composer's Developer Mode tool picker
|
|
87
|
+
3. Enable "${name}"
|
|
88
|
+
4. Try: "Use mikser_query_entities to show me everything in this catalog"
|
|
89
|
+
|
|
90
|
+
Notes:
|
|
91
|
+
- Mikser must be running and the URL must be reachable from
|
|
92
|
+
OpenAI's servers (not just your machine).
|
|
93
|
+
- Connectors do NOT auto-enable per chat — toggle on for each new
|
|
94
|
+
conversation.
|
|
95
|
+
- Developer mode + arbitrary custom MCP connectors are BETA. Plus /
|
|
96
|
+
Pro / Business / Enterprise / Edu only; Free tier excluded.
|
|
97
|
+
- "ChatGPT Apps" is the GA but read-only variant of MCP integration;
|
|
98
|
+
this script targets the full read+write Developer mode beta.
|
|
99
|
+
|
|
100
|
+
Reference:
|
|
101
|
+
https://help.openai.com/en/articles/12584461
|
|
102
|
+
https://developers.openai.com/apps-sdk/deploy/connect-chatgpt
|
|
103
|
+
`
|
|
104
|
+
)
|
|
105
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// register claude — drop a connector entry into Claude Desktop's
|
|
2
|
+
// config file so the app picks up the local mikser server.
|
|
3
|
+
//
|
|
4
|
+
// Claude Desktop launches MCP servers as stdio subprocesses, so we
|
|
5
|
+
// register supergateway as the launcher — it bridges Claude's stdio
|
|
6
|
+
// to mikser's streamable-HTTP endpoint. The mikser server must be
|
|
7
|
+
// running for Claude to find anything.
|
|
8
|
+
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
|
|
10
|
+
import { homedir, platform } from 'node:os'
|
|
11
|
+
import { join, dirname, resolve } from 'node:path'
|
|
12
|
+
|
|
13
|
+
const DEFAULT_URL = 'http://localhost:3001/mcp'
|
|
14
|
+
|
|
15
|
+
function configPath() {
|
|
16
|
+
const home = homedir()
|
|
17
|
+
switch (platform()) {
|
|
18
|
+
case 'darwin': return join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
|
|
19
|
+
case 'win32': return join(process.env.APPDATA ?? join(home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json')
|
|
20
|
+
case 'linux': return join(home, '.config', 'Claude', 'claude_desktop_config.json')
|
|
21
|
+
default: throw new Error(`register-mcp: unsupported platform "${platform()}"`)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseArgs(args) {
|
|
26
|
+
const opts = {}
|
|
27
|
+
for (const a of args) {
|
|
28
|
+
if (a === '--unregister') opts.unregister = true
|
|
29
|
+
else if (a === '--dry-run') opts.dryRun = true
|
|
30
|
+
else if (a === '--force') opts.force = true
|
|
31
|
+
else if (a.startsWith('--url=')) opts.url = a.slice(6)
|
|
32
|
+
else if (a === '--url') opts._expectUrl = true
|
|
33
|
+
else if (opts._expectUrl) { opts.url = a; opts._expectUrl = false }
|
|
34
|
+
}
|
|
35
|
+
return opts
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readProjectName() {
|
|
39
|
+
const pkgPath = resolve(process.cwd(), 'package.json')
|
|
40
|
+
if (!existsSync(pkgPath)) {
|
|
41
|
+
throw new Error(`No package.json in ${process.cwd()} — run this from inside a mikser project directory.`)
|
|
42
|
+
}
|
|
43
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
44
|
+
if (!pkg.name) throw new Error('package.json has no "name" field — set one before registering.')
|
|
45
|
+
return pkg.name
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function runClaude(args) {
|
|
49
|
+
const opts = parseArgs(args)
|
|
50
|
+
const name = readProjectName()
|
|
51
|
+
const url = opts.url ?? DEFAULT_URL
|
|
52
|
+
|
|
53
|
+
const entry = {
|
|
54
|
+
command: 'npx',
|
|
55
|
+
args: ['-y', 'supergateway', '--streamableHttp', url],
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const path = configPath()
|
|
59
|
+
let config = {}
|
|
60
|
+
let existed = false
|
|
61
|
+
|
|
62
|
+
if (existsSync(path)) {
|
|
63
|
+
existed = true
|
|
64
|
+
try {
|
|
65
|
+
config = JSON.parse(readFileSync(path, 'utf8'))
|
|
66
|
+
} catch (err) {
|
|
67
|
+
process.stderr.write(`! Existing Claude Desktop config at\n ${path}\nis not valid JSON. Refusing to overwrite — fix it manually first.\n Error: ${err.message}\n`)
|
|
68
|
+
process.exit(1)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
config.mcpServers = config.mcpServers || {}
|
|
73
|
+
|
|
74
|
+
// Unregister branch.
|
|
75
|
+
if (opts.unregister) {
|
|
76
|
+
if (!(name in config.mcpServers)) {
|
|
77
|
+
process.stdout.write(`✓ "${name}" is not registered. Nothing to do.\n`)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
if (opts.dryRun) {
|
|
81
|
+
process.stdout.write(`[dry-run] Would remove "${name}" from ${path}\n`)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
delete config.mcpServers[name]
|
|
85
|
+
writeFileSync(path, JSON.stringify(config, null, 2) + '\n')
|
|
86
|
+
process.stdout.write(`✓ Removed "${name}" from ${path}\n Restart Claude Desktop to apply.\n`)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Idempotent — bail if already matching.
|
|
91
|
+
const existing = config.mcpServers[name]
|
|
92
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(entry)) {
|
|
93
|
+
process.stdout.write(`✓ "${name}" is already registered with the matching config.\n Nothing to change. (Use --force to re-write anyway.)\n`)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
if (existing && !opts.force) {
|
|
97
|
+
process.stderr.write(`! "${name}" is already registered but with a different config:\n ${JSON.stringify(existing)}\n Wanted:\n ${JSON.stringify(entry)}\n Re-run with --force to overwrite.\n`)
|
|
98
|
+
process.exit(1)
|
|
99
|
+
}
|
|
100
|
+
if (opts.dryRun) {
|
|
101
|
+
process.stdout.write(`[dry-run] Would ${existing ? 'overwrite' : 'add'} "${name}" in ${path}:\n${JSON.stringify(entry, null, 2)}\n`)
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
config.mcpServers[name] = entry
|
|
106
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
107
|
+
writeFileSync(path, JSON.stringify(config, null, 2) + '\n')
|
|
108
|
+
|
|
109
|
+
process.stdout.write(
|
|
110
|
+
`✓ Registered "${name}" in
|
|
111
|
+
${path}
|
|
112
|
+
${existed ? '(merged into existing config)' : '(created new config file)'}
|
|
113
|
+
|
|
114
|
+
Next steps:
|
|
115
|
+
1. Make sure mikser is running and reachable at ${url}
|
|
116
|
+
2. Restart Claude Desktop (fully quit, then reopen)
|
|
117
|
+
3. The mikser tools should appear in Claude's tool list
|
|
118
|
+
|
|
119
|
+
To remove: npx mikser-io-mcp register claude --unregister
|
|
120
|
+
`)
|
|
121
|
+
}
|
package/scripts/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Bin entry for mikser-io-mcp. Dispatches subcommands.
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// npx mikser-io-mcp register claude [--url URL] [--dry-run] [--force] [--unregister]
|
|
6
|
+
// npx mikser-io-mcp register chatgpt --url URL [--dry-run]
|
|
7
|
+
//
|
|
8
|
+
// Run from inside a mikser project directory — registration scripts
|
|
9
|
+
// read ./package.json for the project name (used as the connector
|
|
10
|
+
// label).
|
|
11
|
+
|
|
12
|
+
import { runClaude } from './claude.js'
|
|
13
|
+
import { runChatGPT } from './chatgpt.js'
|
|
14
|
+
|
|
15
|
+
function usage(code = 0) {
|
|
16
|
+
process.stdout.write(`mikser-io-mcp — register this project with an MCP-speaking client
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
npx mikser-io-mcp register claude [options]
|
|
20
|
+
npx mikser-io-mcp register chatgpt --url <URL>
|
|
21
|
+
|
|
22
|
+
Common options:
|
|
23
|
+
--url=<URL> MCP endpoint URL. Default for claude: http://localhost:3001/mcp.
|
|
24
|
+
REQUIRED for chatgpt (must be publicly reachable HTTPS).
|
|
25
|
+
--dry-run Show what would change without writing.
|
|
26
|
+
|
|
27
|
+
Claude-only options:
|
|
28
|
+
--unregister Remove this project's entry from Claude Desktop's config.
|
|
29
|
+
--force Overwrite an existing entry that doesn't match.
|
|
30
|
+
|
|
31
|
+
Run from inside a mikser project directory — the project's package.json
|
|
32
|
+
is read for the connector name.
|
|
33
|
+
`)
|
|
34
|
+
process.exit(code)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const [, , cmd, host, ...rest] = process.argv
|
|
38
|
+
|
|
39
|
+
if (!cmd) usage(0)
|
|
40
|
+
if (cmd === '-h' || cmd === '--help') usage(0)
|
|
41
|
+
|
|
42
|
+
if (cmd !== 'register') {
|
|
43
|
+
process.stderr.write(`Unknown command: ${cmd}\n\n`)
|
|
44
|
+
usage(1)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (host === 'claude') {
|
|
48
|
+
await runClaude(rest)
|
|
49
|
+
} else if (host === 'chatgpt') {
|
|
50
|
+
await runChatGPT(rest)
|
|
51
|
+
} else if (!host) {
|
|
52
|
+
process.stderr.write(`'register' needs a target host: claude | chatgpt\n\n`)
|
|
53
|
+
usage(1)
|
|
54
|
+
} else {
|
|
55
|
+
process.stderr.write(`Unknown host: ${host}. Supported: claude, chatgpt.\n\n`)
|
|
56
|
+
usage(1)
|
|
57
|
+
}
|