@aipoch/open-science 1.0.3 → 1.0.4
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/CLI.md +80 -0
- package/cli.mjs +12 -4
- package/mcp.mjs +310 -0
- package/package.json +10 -6
package/CLI.md
CHANGED
|
@@ -849,3 +849,83 @@ older releases. To switch a file credential back to OS storage, restart in OS mo
|
|
|
849
849
|
replace it with the key while the vault is available. Existing unreadable records remain intact.
|
|
850
850
|
These commands do not migrate historical configuration or store diagnostic history. Older backends that
|
|
851
851
|
lack the endpoints return an endpoint error; the CLI never falls back to editing Settings files directly.
|
|
852
|
+
|
|
853
|
+
## External agents through MCP
|
|
854
|
+
|
|
855
|
+
Start the selected profile normally, then let an MCP client launch the stdio adapter:
|
|
856
|
+
|
|
857
|
+
```bash
|
|
858
|
+
open-science start --no-open --config-root /absolute/path/to/profile
|
|
859
|
+
open-science mcp --config-root /absolute/path/to/profile
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
Example client configuration (replace paths with your installation and profile paths):
|
|
863
|
+
|
|
864
|
+
```json
|
|
865
|
+
{
|
|
866
|
+
"mcpServers": {
|
|
867
|
+
"open-science": {
|
|
868
|
+
"command": "/absolute/path/to/open-science",
|
|
869
|
+
"args": ["mcp", "--config-root", "/absolute/path/to/profile"]
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
`--profile` is an alias for `--config-root`; existing environment overrides and discovery order
|
|
876
|
+
apply. For a source checkout, use `node` as the command and put the absolute path to
|
|
877
|
+
`packages/open-science/cli.mjs` before `mcp` in `args`. The adapter connects to an already
|
|
878
|
+
running authenticated local service; it does not start it, install anything or open a browser.
|
|
879
|
+
`open-science mcp --help` works without a running service.
|
|
880
|
+
|
|
881
|
+
Tool discovery works even when the service is stopped. Calls return an actionable error until
|
|
882
|
+
it is available, and subsequent calls rediscover the service. Tool names consistently use
|
|
883
|
+
lowercase `snake_case`; argument names retain the SDK's `camelCase`. Tools accept explicit IDs
|
|
884
|
+
rather than resolving project names. Each tool publishes its description, input schema, output
|
|
885
|
+
schema and behavior annotations through `tools/list`.
|
|
886
|
+
|
|
887
|
+
| Tool | Purpose |
|
|
888
|
+
| ------------------------------ | -------------------------------------------------------- |
|
|
889
|
+
| `doctor` | Check research readiness |
|
|
890
|
+
| `list_runtimes` | List detected Agent runtimes |
|
|
891
|
+
| `list_projects` | List projects and IDs |
|
|
892
|
+
| `create_project` | Create a persistent project |
|
|
893
|
+
| `update_project` | Update project metadata and Agent Context |
|
|
894
|
+
| `list_sessions` | List sessions, optionally by project |
|
|
895
|
+
| `get_session` | Read session information/status, not a full transcript |
|
|
896
|
+
| `get_session_configuration` | Read session configuration and revision |
|
|
897
|
+
| `update_session_configuration` | Update settings with `expectedRevision` |
|
|
898
|
+
| `start_run` | Submit a prompt and return a run ID |
|
|
899
|
+
| `get_run` | Read progress, output and attention state |
|
|
900
|
+
| `cancel_run` | Explicitly cancel a run without undoing previous effects |
|
|
901
|
+
| `get_plan` | Read the plan and its version/revision |
|
|
902
|
+
| `respond_plan` | Approve/reject a reviewed version or request changes |
|
|
903
|
+
| `list_artifacts` | List artifact metadata without downloading files |
|
|
904
|
+
|
|
905
|
+
A typical workflow calls `list_projects`, `start_run` with `{ "projectId": "…", "prompt": "…" }`,
|
|
906
|
+
then polls `get_run` with the returned run ID and calls `list_artifacts` with the session ID.
|
|
907
|
+
Successful calls return `structuredContent: { "result": ... }` plus the same JSON in text
|
|
908
|
+
content. Tool execution failures set `isError: true`; exception bodies and credentials are not
|
|
909
|
+
forwarded. After a timeout or transport failure, inspect current state before repeating a write:
|
|
910
|
+
the original operation may already have succeeded. The adapter never retries mutations itself.
|
|
911
|
+
|
|
912
|
+
Runs use the service's configured framework and authorization defaults. `start_run` accepts
|
|
913
|
+
existing session, provider/model, permission, skill, specialist, compute-host, memory, review,
|
|
914
|
+
delegation and plan-first options; it does not change the global framework. A run can require
|
|
915
|
+
human action in Open-Science. Explicit `permissionPrompts: "none"` denies unresolved human
|
|
916
|
+
interactions using the existing capability check; it does not expand permissions and cannot
|
|
917
|
+
be combined with `turnIntent: "plan-first"`. Plan approval/rejection requires the version and
|
|
918
|
+
revision from `get_plan`. Configuration updates require the revision from
|
|
919
|
+
`get_session_configuration`.
|
|
920
|
+
|
|
921
|
+
MCP access grants the listed operations for the selected profile under its existing permissions.
|
|
922
|
+
The adapter is not a separate sandbox or a read-only view. Project/configuration changes, plan
|
|
923
|
+
responses and research runs persist through existing business owners and may write files or
|
|
924
|
+
incur model costs. No new database schema, historical-data migration, business status enum,
|
|
925
|
+
credential store or background service is introduced. There is no arbitrary shell/RPC tool,
|
|
926
|
+
credential-management tool, file import/export/download-to-path tool or remote HTTP MCP endpoint.
|
|
927
|
+
|
|
928
|
+
Standard output carries only MCP protocol messages. Closing stdin or sending SIGINT/SIGTERM
|
|
929
|
+
closes the adapter and its pending network requests; it neither stops the service nor cancels
|
|
930
|
+
accepted runs. MCP request cancellation likewise stops waiting, not the persisted operation.
|
|
931
|
+
Use `cancel_run` explicitly to cancel research.
|
package/cli.mjs
CHANGED
|
@@ -40,6 +40,7 @@ Commands:
|
|
|
40
40
|
stop Gracefully stop the backend
|
|
41
41
|
status Show backend status
|
|
42
42
|
url Print the authenticated web URL
|
|
43
|
+
mcp Serve research tools over MCP stdio (--config-root <path> or --profile <path>)
|
|
43
44
|
doctor --json Inspect headless readiness
|
|
44
45
|
runtime list List detected Agent runtimes
|
|
45
46
|
runtime install codex Prepare the app-managed Codex runtime
|
|
@@ -203,6 +204,7 @@ const POSITIONAL_LIMITS = new Map([
|
|
|
203
204
|
['version', 0],
|
|
204
205
|
['help', 1],
|
|
205
206
|
['paths', 0],
|
|
207
|
+
['mcp', 0],
|
|
206
208
|
['doctor', 0],
|
|
207
209
|
['runtime list', 0],
|
|
208
210
|
['runtime install', 1],
|
|
@@ -282,9 +284,11 @@ export const parseCliArgs = (argv) => {
|
|
|
282
284
|
? ['--json', '--help', '-h']
|
|
283
285
|
: command === 'paths'
|
|
284
286
|
? ['--json', '--help', '-h', '--config-root', '--profile']
|
|
285
|
-
: command === '
|
|
286
|
-
? ['--help', '-h']
|
|
287
|
-
:
|
|
287
|
+
: command === 'mcp'
|
|
288
|
+
? ['--help', '-h', '--config-root', '--profile']
|
|
289
|
+
: command === 'help'
|
|
290
|
+
? ['--help', '-h']
|
|
291
|
+
: undefined
|
|
288
292
|
const subcommand =
|
|
289
293
|
(GROUP_COMMANDS.has(command) && args[0] && !args[0].startsWith('-')) ||
|
|
290
294
|
(command === 'run' && (args[0] === 'status' || args[0] === 'cancel'))
|
|
@@ -1878,7 +1882,11 @@ export const runCli = async (argv = process.argv.slice(2)) => {
|
|
|
1878
1882
|
const { version } = await readCliMetadata()
|
|
1879
1883
|
console.log(options.json ? JSON.stringify({ version }) : version)
|
|
1880
1884
|
} else if (command === 'paths') await pathsCommand(options)
|
|
1881
|
-
else if (command === '
|
|
1885
|
+
else if (command === 'mcp') {
|
|
1886
|
+
const { runResearchMcpServer } = await import('./mcp.mjs')
|
|
1887
|
+
const { version } = await readCliMetadata()
|
|
1888
|
+
await runResearchMcpServer({ configRoot: options.configRoot, version })
|
|
1889
|
+
} else if (command === 'init') await initCommand(options)
|
|
1882
1890
|
else if (command === 'start') await startCommand(options)
|
|
1883
1891
|
else if (command === 'stop') await stopCommand(options)
|
|
1884
1892
|
else if (command === 'status') await statusCommand(options)
|
package/mcp.mjs
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
import { connectToOpenScience, OpenScienceApiError } from './index.mjs'
|
|
6
|
+
|
|
7
|
+
const id = (description) => z.string().trim().min(1).describe(description)
|
|
8
|
+
const sessionId = id('Session ID returned by list_sessions or start_run.')
|
|
9
|
+
const projectId = id('Project ID returned by list_projects or create_project.')
|
|
10
|
+
const runId = id('Run ID returned by start_run.')
|
|
11
|
+
const expectedRevision = z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.nonnegative()
|
|
15
|
+
.describe(
|
|
16
|
+
'Current revision returned by the corresponding read tool. Stale revisions are rejected.'
|
|
17
|
+
)
|
|
18
|
+
const agentConfiguration = z.strictObject({
|
|
19
|
+
providerId: id('Configured provider ID.').optional(),
|
|
20
|
+
model: id('Model identifier; null follows the provider default.').nullable().optional(),
|
|
21
|
+
reasoningEffort: z.enum(['default', 'low', 'medium', 'high', 'xhigh', 'max']).optional()
|
|
22
|
+
})
|
|
23
|
+
const sessionSettings = {
|
|
24
|
+
agentConfiguration: agentConfiguration.optional(),
|
|
25
|
+
permissionProfile: z
|
|
26
|
+
.enum(['ask', 'auto', 'full'])
|
|
27
|
+
.describe(
|
|
28
|
+
'Existing authorization policy. Omit to retain configured defaults; full grants broad execution authority.'
|
|
29
|
+
)
|
|
30
|
+
.optional(),
|
|
31
|
+
autoReviewEnabled: z.boolean().optional(),
|
|
32
|
+
memoryEnabled: z.boolean().optional(),
|
|
33
|
+
delegationPolicy: z.enum(['allow', 'deny']).optional()
|
|
34
|
+
}
|
|
35
|
+
const agentContext = z.string().trim().min(1).max(16000)
|
|
36
|
+
const read = {
|
|
37
|
+
readOnlyHint: true,
|
|
38
|
+
destructiveHint: false,
|
|
39
|
+
idempotentHint: true,
|
|
40
|
+
openWorldHint: false
|
|
41
|
+
}
|
|
42
|
+
const write = {
|
|
43
|
+
readOnlyHint: false,
|
|
44
|
+
destructiveHint: true,
|
|
45
|
+
idempotentHint: false,
|
|
46
|
+
openWorldHint: true
|
|
47
|
+
}
|
|
48
|
+
const result = (value) => ({
|
|
49
|
+
content: [{ type: 'text', text: JSON.stringify({ result: value ?? null }) }],
|
|
50
|
+
structuredContent: { result: value ?? null }
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Do not forward raw exception messages: HTTP failures may contain credentials or response bodies.
|
|
54
|
+
const toolError = (error) => {
|
|
55
|
+
const messages = {
|
|
56
|
+
daemon_unavailable:
|
|
57
|
+
'Open-Science is not running. Start it with "open-science start" for the selected profile, then retry.',
|
|
58
|
+
unsupported_capability:
|
|
59
|
+
'The service does not support this operation. Update the service before retrying.',
|
|
60
|
+
project_not_found: 'Project not found. Refresh list_projects.',
|
|
61
|
+
session_not_found: 'Session not found. Refresh list_sessions.',
|
|
62
|
+
run_not_found: 'Run not found. Check the ID returned by start_run.',
|
|
63
|
+
invalid_request: 'Invalid request. Check the tool arguments and service configuration.',
|
|
64
|
+
request_timeout:
|
|
65
|
+
'Request timed out. A write may already have succeeded; check its state before retrying.'
|
|
66
|
+
}
|
|
67
|
+
const code =
|
|
68
|
+
error instanceof OpenScienceApiError && Object.hasOwn(messages, error.code)
|
|
69
|
+
? error.code
|
|
70
|
+
: 'request_failed'
|
|
71
|
+
const status = error instanceof OpenScienceApiError ? error.status : undefined
|
|
72
|
+
const message =
|
|
73
|
+
status === 401 || status === 403
|
|
74
|
+
? 'Access denied. Check the selected profile and its service credentials.'
|
|
75
|
+
: status === 409
|
|
76
|
+
? 'The operation conflicts with current state. Read the latest state and revision before retrying.'
|
|
77
|
+
: (messages[code] ??
|
|
78
|
+
'The operation failed. Check service readiness and request arguments. A write may already have succeeded; inspect its state before retrying.')
|
|
79
|
+
return {
|
|
80
|
+
isError: true,
|
|
81
|
+
content: [{ type: 'text', text: JSON.stringify({ error: { code, message } }) }]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const createResearchMcpServer = ({
|
|
86
|
+
configRoot,
|
|
87
|
+
version,
|
|
88
|
+
connect = connectToOpenScience
|
|
89
|
+
}) => {
|
|
90
|
+
const server = new McpServer({ name: 'open-science', version })
|
|
91
|
+
const lifetime = new AbortController()
|
|
92
|
+
const register = (name, description, shape, annotations, invoke) => {
|
|
93
|
+
server.registerTool(
|
|
94
|
+
name,
|
|
95
|
+
{
|
|
96
|
+
description,
|
|
97
|
+
inputSchema: z.strictObject(shape),
|
|
98
|
+
outputSchema: z.object({ result: z.unknown() }),
|
|
99
|
+
annotations
|
|
100
|
+
},
|
|
101
|
+
async (args, extra) => {
|
|
102
|
+
const signal = AbortSignal.any([lifetime.signal, extra.signal])
|
|
103
|
+
try {
|
|
104
|
+
signal.throwIfAborted()
|
|
105
|
+
// Rediscover per call so service restarts/token rotation recover without retrying writes.
|
|
106
|
+
const client = await connect({ configRoot, signal })
|
|
107
|
+
signal.throwIfAborted()
|
|
108
|
+
return result(await invoke(client, args, { signal }))
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return toolError(error)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
register(
|
|
116
|
+
'doctor',
|
|
117
|
+
'Check whether the running Open-Science service is ready to execute research tasks. Does not start or configure the service.',
|
|
118
|
+
{},
|
|
119
|
+
read,
|
|
120
|
+
(client, _args, options) => client.doctor(options)
|
|
121
|
+
)
|
|
122
|
+
register(
|
|
123
|
+
'list_runtimes',
|
|
124
|
+
'List detected Agent runtimes and readiness. Does not install runtimes or change the selected framework.',
|
|
125
|
+
{},
|
|
126
|
+
read,
|
|
127
|
+
(client, _args, options) => client.listRuntimes(options)
|
|
128
|
+
)
|
|
129
|
+
register(
|
|
130
|
+
'list_projects',
|
|
131
|
+
'List projects and their IDs before choosing a research destination.',
|
|
132
|
+
{},
|
|
133
|
+
read,
|
|
134
|
+
(client, _args, options) => client.listProjects(options)
|
|
135
|
+
)
|
|
136
|
+
register(
|
|
137
|
+
'create_project',
|
|
138
|
+
'Create a persistent research project. Agent Context applies to future work in this project.',
|
|
139
|
+
{
|
|
140
|
+
name: id('Project name.'),
|
|
141
|
+
description: z.string().optional(),
|
|
142
|
+
agentContext: agentContext.optional()
|
|
143
|
+
},
|
|
144
|
+
write,
|
|
145
|
+
(client, args, options) => client.createProject(args, options)
|
|
146
|
+
)
|
|
147
|
+
register(
|
|
148
|
+
'update_project',
|
|
149
|
+
'Update a persistent project. Only supplied fields change; null clears Agent Context.',
|
|
150
|
+
{
|
|
151
|
+
projectId,
|
|
152
|
+
name: id('New project name.').optional(),
|
|
153
|
+
description: z.string().optional(),
|
|
154
|
+
agentContext: agentContext.nullable().optional()
|
|
155
|
+
},
|
|
156
|
+
write,
|
|
157
|
+
(client, { projectId, ...patch }, options) => client.updateProject(projectId, patch, options)
|
|
158
|
+
)
|
|
159
|
+
register(
|
|
160
|
+
'list_sessions',
|
|
161
|
+
'List sessions, optionally within one project. Use the returned ID to inspect or continue a session.',
|
|
162
|
+
{
|
|
163
|
+
projectId: projectId.optional()
|
|
164
|
+
},
|
|
165
|
+
read,
|
|
166
|
+
(client, args, options) => client.listSessions(args.projectId, options)
|
|
167
|
+
)
|
|
168
|
+
register(
|
|
169
|
+
'get_session',
|
|
170
|
+
'Read session information and current status. This is not a full conversation transcript.',
|
|
171
|
+
{ sessionId },
|
|
172
|
+
read,
|
|
173
|
+
(client, args, options) => client.getSession(args.sessionId, options)
|
|
174
|
+
)
|
|
175
|
+
register(
|
|
176
|
+
'get_session_configuration',
|
|
177
|
+
'Read current session configuration and revision before changing it.',
|
|
178
|
+
{ sessionId },
|
|
179
|
+
read,
|
|
180
|
+
(client, args, options) => client.getSessionConfiguration(args.sessionId, options)
|
|
181
|
+
)
|
|
182
|
+
register(
|
|
183
|
+
'update_session_configuration',
|
|
184
|
+
'Update persistent session settings using the latest revision. May change model costs and execution permissions; only supplied fields change.',
|
|
185
|
+
{
|
|
186
|
+
sessionId,
|
|
187
|
+
expectedRevision,
|
|
188
|
+
...sessionSettings,
|
|
189
|
+
computeHosts: z
|
|
190
|
+
.strictObject({
|
|
191
|
+
enabled: z.array(id('Enabled Compute Host ID.')),
|
|
192
|
+
selected: z.array(id('Selected Compute Host ID.'))
|
|
193
|
+
})
|
|
194
|
+
.optional()
|
|
195
|
+
},
|
|
196
|
+
write,
|
|
197
|
+
(client, { sessionId, ...patch }, options) =>
|
|
198
|
+
client.updateSessionConfiguration(sessionId, patch, options)
|
|
199
|
+
)
|
|
200
|
+
register(
|
|
201
|
+
'start_run',
|
|
202
|
+
'Submit research in a project and immediately return a run ID. Omit sessionId to create a session, or provide it to continue that session through its existing active branch. May write files/data and incur model charges. Poll get_run; disconnecting does not cancel the run. Uses configured framework and permissions, without automatically approving human interactions.',
|
|
203
|
+
{
|
|
204
|
+
projectId,
|
|
205
|
+
prompt: id('Research instructions.'),
|
|
206
|
+
sessionId: sessionId.optional(),
|
|
207
|
+
cwd: id('Working directory on the Open-Science host.').optional(),
|
|
208
|
+
...sessionSettings,
|
|
209
|
+
skillIds: z.array(id('Skill ID.')).optional(),
|
|
210
|
+
specialist: id('Specialist ID or exact name for a new session.').optional(),
|
|
211
|
+
turnIntent: z
|
|
212
|
+
.literal('plan-first')
|
|
213
|
+
.describe('Require an approved plan before execution.')
|
|
214
|
+
.optional(),
|
|
215
|
+
permissionPrompts: z
|
|
216
|
+
.literal('none')
|
|
217
|
+
.describe(
|
|
218
|
+
'Deny unresolved human interactions instead of waiting; does not expand permissions. Incompatible with plan-first.'
|
|
219
|
+
)
|
|
220
|
+
.optional(),
|
|
221
|
+
computeHostIds: z.array(id('Selected Compute Host ID.')).optional(),
|
|
222
|
+
enabledComputeHostIds: z.array(id('Enabled Compute Host ID for a new session.')).optional()
|
|
223
|
+
},
|
|
224
|
+
write,
|
|
225
|
+
(client, { projectId, ...request }, options) => {
|
|
226
|
+
if (request.turnIntent === 'plan-first' && request.permissionPrompts === 'none') {
|
|
227
|
+
throw new OpenScienceApiError('Incompatible permission and planning options.', {
|
|
228
|
+
code: 'invalid_request'
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
return client.startRun({ project: projectId, ...request }, options)
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
register(
|
|
235
|
+
'get_run',
|
|
236
|
+
'Read authoritative run status, progress, result and attention state. Poll this after start_run; a waiting run may require human action in Open-Science.',
|
|
237
|
+
{ runId },
|
|
238
|
+
read,
|
|
239
|
+
(client, args, options) => client.getRun(args.runId, options)
|
|
240
|
+
)
|
|
241
|
+
register(
|
|
242
|
+
'cancel_run',
|
|
243
|
+
'Explicitly cancel a research run and wait for service finalization. This does not undo files, data or costs already produced.',
|
|
244
|
+
{ runId },
|
|
245
|
+
write,
|
|
246
|
+
(client, args, options) => client.cancelRun(args.runId, options)
|
|
247
|
+
)
|
|
248
|
+
register(
|
|
249
|
+
'get_plan',
|
|
250
|
+
'Read the current session plan, artifact version and revision, or null when there is no plan.',
|
|
251
|
+
{ sessionId },
|
|
252
|
+
read,
|
|
253
|
+
(client, args, options) => client.getSessionPlan(args.sessionId, options)
|
|
254
|
+
)
|
|
255
|
+
register(
|
|
256
|
+
'respond_plan',
|
|
257
|
+
'Explicitly approve or reject the reviewed plan version, or request revision with feedback. Approval may resume execution and incur costs. Never approve without reviewing get_plan.',
|
|
258
|
+
{
|
|
259
|
+
sessionId,
|
|
260
|
+
response: z.union([
|
|
261
|
+
z.strictObject({
|
|
262
|
+
decision: z.enum(['approved', 'rejected']),
|
|
263
|
+
artifactVersionId: id('Plan artifact version returned by get_plan.'),
|
|
264
|
+
expectedRevision
|
|
265
|
+
}),
|
|
266
|
+
z.strictObject({ feedback: id('Requested changes to the plan.') })
|
|
267
|
+
])
|
|
268
|
+
},
|
|
269
|
+
write,
|
|
270
|
+
(client, args, options) => client.respondSessionPlan(args.sessionId, args.response, options)
|
|
271
|
+
)
|
|
272
|
+
register(
|
|
273
|
+
'list_artifacts',
|
|
274
|
+
'List generated artifact metadata for a session. Does not download files or write local paths.',
|
|
275
|
+
{ sessionId },
|
|
276
|
+
read,
|
|
277
|
+
(client, args, options) => client.listArtifacts(args.sessionId, options)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
const onclose = server.server.onclose
|
|
281
|
+
server.server.onclose = () => {
|
|
282
|
+
lifetime.abort()
|
|
283
|
+
onclose?.()
|
|
284
|
+
}
|
|
285
|
+
return server
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export const runResearchMcpServer = async (options) => {
|
|
289
|
+
const server = createResearchMcpServer(options)
|
|
290
|
+
const transport = new StdioServerTransport()
|
|
291
|
+
let closing
|
|
292
|
+
const close = () => {
|
|
293
|
+
closing ??= server.close().finally(() => {
|
|
294
|
+
process.stdin.off('end', close)
|
|
295
|
+
process.off('SIGINT', close)
|
|
296
|
+
process.off('SIGTERM', close)
|
|
297
|
+
})
|
|
298
|
+
return closing
|
|
299
|
+
}
|
|
300
|
+
process.stdin.once('end', close)
|
|
301
|
+
process.once('SIGINT', close)
|
|
302
|
+
process.once('SIGTERM', close)
|
|
303
|
+
try {
|
|
304
|
+
await server.connect(transport)
|
|
305
|
+
if (process.stdin.readableEnded) await close()
|
|
306
|
+
} catch (error) {
|
|
307
|
+
await close()
|
|
308
|
+
throw error
|
|
309
|
+
}
|
|
310
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aipoch/open-science",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Node.js SDK and CLI for the Open-Science local daemon",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -37,11 +37,15 @@
|
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
42
|
+
"zod": "^4.6.5"
|
|
43
|
+
},
|
|
40
44
|
"optionalDependencies": {
|
|
41
|
-
"@aipoch/open-science-darwin-arm64": "1.0.
|
|
42
|
-
"@aipoch/open-science-darwin-x64": "1.0.
|
|
43
|
-
"@aipoch/open-science-linux-x64-gnu": "1.0.
|
|
44
|
-
"@aipoch/open-science-linux-arm64-gnu": "1.0.
|
|
45
|
-
"@aipoch/open-science-win32-x64": "1.0.
|
|
45
|
+
"@aipoch/open-science-darwin-arm64": "1.0.4",
|
|
46
|
+
"@aipoch/open-science-darwin-x64": "1.0.4",
|
|
47
|
+
"@aipoch/open-science-linux-x64-gnu": "1.0.4",
|
|
48
|
+
"@aipoch/open-science-linux-arm64-gnu": "1.0.4",
|
|
49
|
+
"@aipoch/open-science-win32-x64": "1.0.4"
|
|
46
50
|
}
|
|
47
51
|
}
|