@octane-xplat/mcp 0.15.0 → 0.16.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.
Files changed (67) hide show
  1. package/README.md +9 -5
  2. package/dist/_virtual/_rolldown/runtime.js +27 -0
  3. package/dist/actions/bridge.js +1 -0
  4. package/dist/context.js +0 -0
  5. package/dist/contracts/actions.js +1 -0
  6. package/dist/contracts/boundaries.js +1 -0
  7. package/dist/contracts/capabilities.js +1 -0
  8. package/dist/contracts/code.js +1 -0
  9. package/dist/contracts/errors.js +8 -0
  10. package/dist/contracts/evidence.js +8 -0
  11. package/dist/contracts/identity.js +8 -0
  12. package/dist/contracts/index.js +1 -0
  13. package/dist/contracts/inspection.js +1 -0
  14. package/dist/contracts/policy.js +8 -0
  15. package/dist/contracts/sessions.js +1 -0
  16. package/dist/contracts/status.js +1 -0
  17. package/dist/discovery.js +8 -0
  18. package/dist/docs.js +63 -0
  19. package/dist/inspection/service.js +3 -0
  20. package/dist/inspection/tools.js +43 -0
  21. package/dist/runtime/logs.js +1 -0
  22. package/dist/runtime/process.js +1 -0
  23. package/dist/runtime/service.js +1 -0
  24. package/dist/runtime/targets.js +1 -0
  25. package/dist/runtime/tools.js +119 -0
  26. package/dist/server.js +324 -0
  27. package/dist/status.js +8 -0
  28. package/dist/xplat-mcp.js +68 -0
  29. package/package.json +18 -12
  30. package/src/context.ts +108 -0
  31. package/src/{docs.mjs → docs.ts} +31 -26
  32. package/src/inspection/{service.mjs → service.ts} +1 -1
  33. package/src/inspection/{tools.mjs → tools.ts} +13 -5
  34. package/src/runtime/{tools.mjs → tools.ts} +16 -6
  35. package/src/{server.mjs → server.ts} +48 -33
  36. package/src/{xplat-mcp.mjs → xplat-mcp.ts} +21 -10
  37. /package/src/actions/{bridge.mjs → bridge.ts} +0 -0
  38. /package/src/contracts/{actions.mjs → actions.ts} +0 -0
  39. /package/src/contracts/{boundaries.mjs → boundaries.ts} +0 -0
  40. /package/src/contracts/{capabilities.mjs → capabilities.ts} +0 -0
  41. /package/src/contracts/{code.mjs → code.ts} +0 -0
  42. /package/src/contracts/{errors.mjs → errors.ts} +0 -0
  43. /package/src/contracts/{evidence.mjs → evidence.ts} +0 -0
  44. /package/src/contracts/{identity.mjs → identity.ts} +0 -0
  45. /package/src/contracts/{index.mjs → index.ts} +0 -0
  46. /package/src/contracts/{inspection.mjs → inspection.ts} +0 -0
  47. /package/src/contracts/{policy.mjs → policy.ts} +0 -0
  48. /package/src/contracts/{sessions.mjs → sessions.ts} +0 -0
  49. /package/src/contracts/{status.mjs → status.ts} +0 -0
  50. /package/src/{discovery.mjs → discovery.ts} +0 -0
  51. /package/src/runtime/{logs.mjs → logs.ts} +0 -0
  52. /package/src/runtime/{process.mjs → process.ts} +0 -0
  53. /package/src/runtime/{service.mjs → service.ts} +0 -0
  54. /package/src/runtime/{targets.mjs → targets.ts} +0 -0
  55. /package/src/{status.mjs → status.ts} +0 -0
  56. /package/{src → types}/contracts/actions.d.ts +0 -0
  57. /package/{src → types}/contracts/boundaries.d.ts +0 -0
  58. /package/{src → types}/contracts/capabilities.d.ts +0 -0
  59. /package/{src → types}/contracts/code.d.ts +0 -0
  60. /package/{src → types}/contracts/errors.d.ts +0 -0
  61. /package/{src → types}/contracts/evidence.d.ts +0 -0
  62. /package/{src → types}/contracts/identity.d.ts +0 -0
  63. /package/{src → types}/contracts/index.d.ts +0 -0
  64. /package/{src → types}/contracts/inspection.d.ts +0 -0
  65. /package/{src → types}/contracts/policy.d.ts +0 -0
  66. /package/{src → types}/contracts/sessions.d.ts +0 -0
  67. /package/{src → types}/contracts/status.d.ts +0 -0
@@ -2,13 +2,21 @@
2
2
  // shared layer at @octane-xplat/cli/mcp/inspection/service; this file only
3
3
  // binds it to MCP tools.
4
4
  import { z } from 'zod'
5
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
6
+ import type { Respond, WorkspaceContext } from '../context.ts'
5
7
 
6
- export function registerInspectionTools(server, context, respond) {
8
+ export function registerInspectionTools(
9
+ server: McpServer,
10
+ context: WorkspaceContext,
11
+ respond: Respond,
12
+ ) {
7
13
  const control = z.object({ kind: z.enum(['id', 'testID']), name: z.string().min(1).max(200) })
8
- const run = (method) => async (args) => {
9
- const data = await context.inspection[method](args)
10
- return data.error ? respond({ ok: false, error: data.error }) : respond({ ok: true, data })
11
- }
14
+ const run =
15
+ (method: 'inspect' | 'interact' | 'screenshot') =>
16
+ async (args: any): Promise<ReturnType<Respond>> => {
17
+ const data = await context.inspection[method](args)
18
+ return data.error ? respond({ ok: false, error: data.error }) : respond({ ok: true, data })
19
+ }
12
20
 
13
21
  server.registerTool(
14
22
  'xplat_inspect',
@@ -3,9 +3,16 @@
3
3
  // contracts.
4
4
 
5
5
  import { z } from 'zod'
6
- import { PLATFORMS } from '../contracts/identity.mjs'
6
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
7
+ import { PLATFORMS } from '../contracts/identity.ts'
8
+ import type { Respond, WorkspaceContext } from '../context.ts'
9
+ import type { Platform } from '../../types/contracts/identity'
7
10
 
8
- export function registerRuntimeTools(server, context, respond) {
11
+ export function registerRuntimeTools(
12
+ server: McpServer,
13
+ context: WorkspaceContext,
14
+ respond: Respond,
15
+ ) {
9
16
  const runtime = context.runtime
10
17
 
11
18
  server.registerTool(
@@ -14,7 +21,10 @@ export function registerRuntimeTools(server, context, respond) {
14
21
  description:
15
22
  'Discover launchable targets for the bound app: simulators, emulators, devices and host targets with availability, missing-toolchain reasons, and session busy/ownership state.',
16
23
  inputSchema: {
17
- platform: z.enum(PLATFORMS).optional().describe('Limit to one platform'),
24
+ platform: z
25
+ .enum(PLATFORMS as Platform[])
26
+ .optional()
27
+ .describe('Limit to one platform'),
18
28
  },
19
29
  },
20
30
  async ({ platform }) => {
@@ -31,7 +41,7 @@ export function registerRuntimeTools(server, context, respond) {
31
41
  description:
32
42
  'Build the bound app for one platform as a background job (the same commands `xplat build` runs). Returns a job id — poll xplat_job_status and read output with xplat_logs. Repeating with the same requestKey returns the live job instead of duplicating it.',
33
43
  inputSchema: {
34
- platform: z.enum(PLATFORMS),
44
+ platform: z.enum(PLATFORMS as Platform[]),
35
45
  requestKey: z.string().optional().describe('Idempotence key for retries'),
36
46
  },
37
47
  },
@@ -49,7 +59,7 @@ export function registerRuntimeTools(server, context, respond) {
49
59
  description:
50
60
  'Start an owned development session: bind one app to one target and run the platform dev command as a job. The job completes when readiness is confirmed — a failed launch after a successful build reports accurately, and the session keeps the app alive until xplat_session_end.',
51
61
  inputSchema: {
52
- platform: z.enum(PLATFORMS),
62
+ platform: z.enum(PLATFORMS as Platform[]),
53
63
  targetId: z
54
64
  .string()
55
65
  .optional()
@@ -77,7 +87,7 @@ export function registerRuntimeTools(server, context, respond) {
77
87
  description:
78
88
  'Attach to a compatible development session that is already running — never restarts or terminates it. Reports candidates per platform; attach binds a preserved user-owned session.',
79
89
  inputSchema: {
80
- platform: z.enum(PLATFORMS),
90
+ platform: z.enum(PLATFORMS as Platform[]),
81
91
  targetId: z
82
92
  .string()
83
93
  .optional()
@@ -4,20 +4,27 @@
4
4
 
5
5
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
6
6
  import { readFileSync } from 'node:fs'
7
+ import { dirname, join } from 'node:path'
8
+ import { fileURLToPath } from 'node:url'
7
9
  import { z } from 'zod'
8
- import { contractError, toContractError } from './contracts/errors.mjs'
9
- import { EVIDENCE_KINDS } from './contracts/evidence.mjs'
10
- import { PLATFORMS } from './contracts/identity.mjs'
11
- import { PERMISSION_SCOPES } from './contracts/policy.mjs'
12
- import { discoverWorkspace } from './discovery.mjs'
13
- import { registerRuntimeTools } from './runtime/tools.mjs'
14
- import { registerDocsTools } from './docs.mjs'
15
- import { registerInspectionTools } from './inspection/service.mjs'
16
- import { bindApp, buildProjectStatus, resolveSelection, SERVER_NAME } from './status.mjs'
10
+ import { contractError, toContractError } from './contracts/errors.ts'
11
+ import { EVIDENCE_KINDS } from './contracts/evidence.ts'
12
+ import { PLATFORMS } from './contracts/identity.ts'
13
+ import { PERMISSION_SCOPES } from './contracts/policy.ts'
14
+ import { discoverWorkspace } from './discovery.ts'
15
+ import { registerRuntimeTools } from './runtime/tools.ts'
16
+ import { registerDocsTools } from './docs.ts'
17
+ import { registerInspectionTools } from './inspection/service.ts'
18
+ import { bindApp, buildProjectStatus, resolveSelection, SERVER_NAME } from './status.ts'
19
+ import type { Respond, Selection, ToolResponse, WorkspaceContext } from './context.ts'
20
+ import type { ContractError } from '../types/contracts/errors'
21
+ import type { AppRef, Platform, WorkspaceKind } from '../types/contracts/identity'
22
+ import type { PermissionScope } from '../types/contracts/policy'
23
+ import type { EvidenceKind } from '../types/contracts/evidence'
17
24
 
18
25
  // Lockstep releases bump the manifest; the handshake must follow it.
19
26
  const SERVER_VERSION = JSON.parse(
20
- readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
27
+ readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8'),
21
28
  ).version
22
29
 
23
30
  const INSTRUCTIONS =
@@ -27,18 +34,18 @@ const INSTRUCTIONS =
27
34
  'For runtime work, check xplat_capability_matrix then xplat_targets; xplat_launch starts an owned session as a job and xplat_attach binds a session you already run. ' +
28
35
  'Results always say what was actually checked; a successful build is not proof of behavior.'
29
36
 
30
- const respond = (outcome) => ({
37
+ const respond: Respond = (outcome) => ({
31
38
  structuredContent: outcome,
32
39
  isError: outcome.ok === false,
33
40
  content: [{ type: 'text', text: JSON.stringify(outcome, null, 2) }],
34
41
  })
35
42
 
36
- const ok = (data) => respond({ ok: true, data })
37
- const fail = (error) => respond({ ok: false, error })
43
+ const ok = (data: unknown): ToolResponse => respond({ ok: true, data })
44
+ const fail = (error: ContractError): ToolResponse => respond({ ok: false, error })
38
45
 
39
46
  /** The bound app for runtime tools — explicit, configured, or sole-app. */
40
- const boundApp = (context) => {
41
- const selection = resolveSelection(context)
47
+ const boundApp = (context: WorkspaceContext): AppRef | null => {
48
+ const selection = resolveSelection(context) as Selection
42
49
  return selection.state === 'bound' ? selection.app : null
43
50
  }
44
51
 
@@ -47,7 +54,7 @@ const NO_APP = () =>
47
54
  recovery: { action: 'Bind an app with xplat_select_app first.' },
48
55
  })
49
56
 
50
- export function createXplatMcpServer(context) {
57
+ export function createXplatMcpServer(context: WorkspaceContext) {
51
58
  const server = new McpServer(
52
59
  { name: SERVER_NAME, version: SERVER_VERSION },
53
60
  { instructions: INSTRUCTIONS },
@@ -56,7 +63,7 @@ export function createXplatMcpServer(context) {
56
63
  // Wire the policy elicitation channel now that the protocol exists. The
57
64
  // client must advertise elicitation support; without it, grants fall back
58
65
  // to developer-side configuration with an honest recovery message.
59
- context.policy.setElicitor(async (scope, reason) => {
66
+ context.policy.setElicitor(async (scope: PermissionScope, reason: string) => {
60
67
  try {
61
68
  if (!server.server.getClientCapabilities()?.elicitation) {
62
69
  return 'denied'
@@ -83,7 +90,7 @@ export function createXplatMcpServer(context) {
83
90
  // search/read a server-side capability — supported on every platform,
84
91
  // independent of runtime adapters.
85
92
  if (context.code) {
86
- for (const platform of PLATFORMS) {
93
+ for (const platform of PLATFORMS as Platform[]) {
87
94
  context.capabilities.register({
88
95
  platform,
89
96
  describe: () => [
@@ -114,9 +121,10 @@ export function createXplatMcpServer(context) {
114
121
  if (refresh) {
115
122
  context.discovery = discoverWorkspace(context.workspaceRoot)
116
123
  // A previously bound app may have moved or vanished; re-resolve.
117
- if (context.boundApp) {
118
- const still = context.discovery.apps.find((a) => a.id === context.boundApp.id)
119
- context.boundApp = still ?? null
124
+ const bound = context.boundApp
125
+ if (bound) {
126
+ const still = context.discovery.apps.find((a) => a.id === bound.id)
127
+ context.boundApp = (still ?? null) as AppRef | null
120
128
  context.selectionReason = still ? 'explicit' : null
121
129
  }
122
130
  }
@@ -164,13 +172,16 @@ export function createXplatMcpServer(context) {
164
172
  description:
165
173
  'Per-platform capability matrix: build, launch, logs, UI inspection, screenshot, and interaction are separate capabilities. Unsupported or missing capabilities come with an explanation and a usable alternative when one exists.',
166
174
  inputSchema: {
167
- platform: z.enum(PLATFORMS).optional().describe('Limit to one platform'),
175
+ platform: z
176
+ .enum(PLATFORMS as Platform[])
177
+ .optional()
178
+ .describe('Limit to one platform'),
168
179
  },
169
180
  },
170
181
  async ({ platform }) => {
171
182
  const status = await buildProjectStatus(context)
172
183
  const matrices = platform
173
- ? status.capabilities.filter((m) => m.platform === platform)
184
+ ? status.capabilities.filter((m: { platform: string }) => m.platform === platform)
174
185
  : status.capabilities
175
186
 
176
187
  return ok({ capabilities: matrices })
@@ -183,7 +194,7 @@ export function createXplatMcpServer(context) {
183
194
  description:
184
195
  'Report permission grants and their states. Workspace scope and ordinary runtime actions reuse the task authorization; screenshots require an explicit per-task developer grant checked at action time.',
185
196
  inputSchema: {
186
- scope: z.enum(PERMISSION_SCOPES).optional(),
197
+ scope: z.enum(PERMISSION_SCOPES as PermissionScope[]).optional(),
187
198
  },
188
199
  },
189
200
  async ({ scope }) => {
@@ -364,7 +375,10 @@ export function createXplatMcpServer(context) {
364
375
  .string()
365
376
  .optional()
366
377
  .describe('ISO expiration for retain; omit for manual deletion only'),
367
- kind: z.enum(EVIDENCE_KINDS).optional().describe('Filter for list'),
378
+ kind: z
379
+ .enum(EVIDENCE_KINDS as EvidenceKind[])
380
+ .optional()
381
+ .describe('Filter for list'),
368
382
  },
369
383
  },
370
384
  async ({ op, id, expiresAt, kind }) => {
@@ -378,21 +392,22 @@ export function createXplatMcpServer(context) {
378
392
 
379
393
  if (op === 'retain') {
380
394
  const result = context.evidence.retain(id, { expiresAt: expiresAt ?? null })
381
- return result.error ? fail(result.error) : ok(result)
395
+ return 'error' in result ? fail(result.error) : ok(result)
382
396
  }
383
397
 
384
398
  const result = context.evidence.delete(id)
385
- return result.error ? fail(result.error) : ok(result)
399
+ return 'error' in result ? fail(result.error) : ok(result)
386
400
  },
387
401
  )
388
402
 
389
403
  // Code retrieval (work item C). Results are leads with provenance and
390
404
  // freshness — exact search reads live files, semantic hits are
391
405
  // revalidated against current source before serving.
392
- const boundAppForCode = () => {
393
- const selection = resolveSelection(context)
406
+ const boundAppForCode = (): { app: AppRef | null; error?: ContractError } => {
407
+ const selection = resolveSelection(context) as Selection
394
408
  if (selection.state === 'ambiguous') {
395
409
  return {
410
+ app: null,
396
411
  error: contractError(
397
412
  'ambiguous_selection',
398
413
  'Several apps are detected — code retrieval targets the bound app and its installed packages, never a guessed one.',
@@ -424,7 +439,7 @@ export function createXplatMcpServer(context) {
424
439
  ),
425
440
  )
426
441
 
427
- const codeResult = (result) => {
442
+ const codeResult = (result: any): ToolResponse => {
428
443
  if (!result?.status) {
429
444
  return ok(result)
430
445
  }
@@ -456,7 +471,7 @@ export function createXplatMcpServer(context) {
456
471
  .describe('exact = token search over current files; semantic = local index; auto = both'),
457
472
  corpus: z.enum(['app', 'framework', 'all']).optional().describe('Default: all'),
458
473
  platform: z
459
- .enum(PLATFORMS)
474
+ .enum(PLATFORMS as Platform[])
460
475
  .optional()
461
476
  .describe('Include shared files plus files marked for this platform'),
462
477
  package: z.string().optional().describe('Limit the framework corpus to one package'),
@@ -482,7 +497,7 @@ export function createXplatMcpServer(context) {
482
497
  return codeResult(
483
498
  await context.code.search({
484
499
  app,
485
- workspaceKind: context.discovery.workspace.kind,
500
+ workspaceKind: context.discovery.workspace.kind as WorkspaceKind,
486
501
  query,
487
502
  mode,
488
503
  corpus,
@@ -533,7 +548,7 @@ export function createXplatMcpServer(context) {
533
548
  return codeResult(
534
549
  await context.code.read({
535
550
  app,
536
- workspaceKind: context.discovery.workspace.kind,
551
+ workspaceKind: context.discovery.workspace.kind as WorkspaceKind,
537
552
  ref,
538
553
  corpus,
539
554
  package: pkg,
@@ -5,9 +5,13 @@
5
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
6
6
  import { createCodeService } from '@octane-xplat/cli/mcp/code-service'
7
7
  import { readFileSync } from 'node:fs'
8
- import { PERMISSION_SCOPES } from './contracts/policy.mjs'
9
- import { createXplatMcpServer } from './server.mjs'
10
- import { createWorkspaceContext } from './status.mjs'
8
+ import { dirname, join } from 'node:path'
9
+ import { fileURLToPath } from 'node:url'
10
+ import { PERMISSION_SCOPES } from './contracts/policy.ts'
11
+ import { createXplatMcpServer } from './server.ts'
12
+ import { createWorkspaceContext } from './status.ts'
13
+ import type { WorkspaceContext } from './context.ts'
14
+ import type { PermissionScope } from '../types/contracts/policy'
11
15
 
12
16
  const usage = `xplat-mcp — Octane Xplat MCP server (stdio)
13
17
 
@@ -22,7 +26,7 @@ Workspace reads and ordinary runtime actions reuse the developer's task
22
26
  authorization; visual-capture always needs an explicit per-task grant.
23
27
  `
24
28
 
25
- const arg = (name) => {
29
+ const arg = (name: string): string | undefined => {
26
30
  const index = process.argv.indexOf(`--${name}`)
27
31
  return index >= 0 ? process.argv[index + 1] : undefined
28
32
  }
@@ -37,7 +41,7 @@ const permissionScopes = (arg('permissions') ?? process.env.XPLAT_MCP_PERMISSION
37
41
  .map((s) => s.trim())
38
42
  .filter(Boolean)
39
43
 
40
- const unknown = permissionScopes.filter((s) => !PERMISSION_SCOPES.includes(s))
44
+ const unknown = permissionScopes.filter((s) => !PERMISSION_SCOPES.includes(s as PermissionScope))
41
45
  if (unknown.length) {
42
46
  process.stderr.write(`xplat-mcp: ignoring unknown permission scopes: ${unknown.join(', ')}\n`)
43
47
  }
@@ -46,19 +50,25 @@ const workspaceRoot = arg('workspace') ?? process.env.XPLAT_MCP_WORKSPACE ?? pro
46
50
 
47
51
  // The status report's `server` field identifies this entry point — the
48
52
  // manifest version follows lockstep bumps automatically.
49
- const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
53
+ const manifest = JSON.parse(
54
+ readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8'),
55
+ )
50
56
 
51
- const context = createWorkspaceContext({
57
+ const context: WorkspaceContext = createWorkspaceContext({
52
58
  workspaceRoot,
53
59
  serverIdentity: { name: '@octane-xplat/mcp', version: manifest.version },
54
60
  appSelector: arg('app') ?? process.env.XPLAT_MCP_APP,
55
- permissionScopes: permissionScopes.filter((s) => PERMISSION_SCOPES.includes(s)),
61
+ permissionScopes: permissionScopes.filter((s): s is PermissionScope =>
62
+ PERMISSION_SCOPES.includes(s as PermissionScope),
63
+ ),
56
64
  evidenceRoot: arg('evidence-dir') ?? process.env.XPLAT_MCP_EVIDENCE_DIR,
57
65
  code: createCodeService({
58
66
  workspaceRoot,
59
67
  workspaceKind: () => context.discovery.workspace.kind,
60
68
  }),
61
- })
69
+ // The shared layer attaches docs/devActions/runtime/inspection providers
70
+ // after creating the literal — the .mjs inference cannot see them.
71
+ }) as unknown as WorkspaceContext
62
72
 
63
73
  const { server, shutdown } = createXplatMcpServer(context)
64
74
 
@@ -72,7 +82,8 @@ const close = async () => {
72
82
  try {
73
83
  await shutdown()
74
84
  } catch (error) {
75
- process.stderr.write(`xplat-mcp: shutdown cleanup failed: ${error?.message ?? error}\n`)
85
+ const detail = error instanceof Error ? error.message : String(error)
86
+ process.stderr.write(`xplat-mcp: shutdown cleanup failed: ${detail}\n`)
76
87
  } finally {
77
88
  await server.close()
78
89
  process.exit(0)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes