Skip to content

Commit 64db03b

Browse files
a6b8claude
andcommitted
Fix dual-storage and add call validation
Closes #6 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0af265a commit 64db03b

3 files changed

Lines changed: 112 additions & 33 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ Interactive commands for humans. These use prompts, colored output, and guided w
4242
| `flowmcp init` | Interactive setup — creates global and local config |
4343
| `flowmcp help` | Show available commands and usage |
4444

45+
### Mode Availability
46+
47+
`init` and other development commands are available based on the current mode:
48+
49+
| Situation | Mode | `init` | `validate`/`test` | `search`/`add` |
50+
|-----------|------|--------|--------------------|-----------------|
51+
| No local config | `null` | Yes | Yes | Yes |
52+
| Config with `mode: "agent"` | `agent` | No | No | Yes |
53+
| Config with `mode: "dev"` | `development` | Yes | Yes | Yes |
54+
55+
When no local config exists, all commands are available so that `flowmcp init` can be run to bootstrap a project. Once the config is set to `agent` mode, only agent commands remain accessible.
56+
4557
## Agent Commands
4658

4759
JSON-output commands designed for programmatic consumption by AI agents and scripts.

src/index.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ const runDevCommands = async () => {
234234
const main = async () => {
235235
if( values[ 'help' ] || !command ) {
236236
const { result: { mode } } = await FlowMcpCli.getMode( { cwd } )
237-
if( mode === MODE_DEVELOPMENT ) {
237+
if( mode === MODE_DEVELOPMENT || mode === null ) {
238238
await FlowMcpCli.help( { cwd } )
239239
} else {
240240
await FlowMcpCli.helpAgent()
@@ -250,7 +250,7 @@ const main = async () => {
250250
return
251251
}
252252

253-
if( mode === MODE_DEVELOPMENT ) {
253+
if( mode === MODE_DEVELOPMENT || mode === null ) {
254254
const devHandled = await runDevCommands()
255255
if( devHandled ) {
256256
return
@@ -264,7 +264,7 @@ const main = async () => {
264264
const result = {
265265
'status': false,
266266
'error': `Unknown command "${command}".`,
267-
'available': [ 'search', 'add', 'remove', 'list', 'call', 'status', 'mode' ],
267+
'available': agentCommands,
268268
'fix': `Run: flowmcp --help`
269269
}
270270

src/task/FlowMcpCli.mjs

Lines changed: 97 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class FlowMcpCli {
137137
const { data: existingLocalConfig } = await FlowMcpCli.#readJson( { filePath: localConfigPath } )
138138

139139
const localConfigUpdates = {
140+
'mode': MODE_AGENT,
140141
'root': `~/${appConfig[ 'globalConfigDirName' ]}`
141142
}
142143

@@ -278,7 +279,11 @@ class FlowMcpCli {
278279
const localGroupConfigPath = join( localGroupDir, 'config.json' )
279280

280281
const { data: currentLocalConfig } = await FlowMcpCli.#readJson( { filePath: localGroupConfigPath } )
281-
const updatedLocalConfig = currentLocalConfig || { 'root': `~/${appConfig[ 'globalConfigDirName' ]}` }
282+
const updatedLocalConfig = currentLocalConfig || { 'mode': MODE_AGENT, 'root': `~/${appConfig[ 'globalConfigDirName' ]}` }
283+
284+
if( !updatedLocalConfig[ 'mode' ] ) {
285+
updatedLocalConfig[ 'mode' ] = MODE_AGENT
286+
}
282287

283288
if( !updatedLocalConfig[ 'groups' ] ) {
284289
updatedLocalConfig[ 'groups' ] = {}
@@ -1981,6 +1986,8 @@ class FlowMcpCli {
19811986

19821987
let matchedFunc = null
19831988
let matchedToolName = null
1989+
let matchedRouteName = null
1990+
let matchedSchema = null
19841991

19851992
resolvedSchemas
19861993
.forEach( ( { schema } ) => {
@@ -2008,6 +2015,8 @@ class FlowMcpCli {
20082015
if( prepared[ 'toolName' ] === toolName ) {
20092016
matchedFunc = prepared[ 'func' ]
20102017
matchedToolName = prepared[ 'toolName' ]
2018+
matchedRouteName = routeName
2019+
matchedSchema = schema
20112020
}
20122021
} catch {
20132022
// skip
@@ -2027,6 +2036,34 @@ class FlowMcpCli {
20272036
return { result }
20282037
}
20292038

2039+
const matchedRouteConfig = matchedSchema[ 'routes' ][ matchedRouteName ]
2040+
const matchedRouteParameters = matchedRouteConfig[ 'parameters' ] || []
2041+
const { parameters: expectedParameters } = FlowMcpCli.#extractParameters( { 'routeParameters': matchedRouteParameters } )
2042+
2043+
const missingParams = Object.entries( expectedParameters )
2044+
.filter( ( [ , paramDef ] ) => {
2045+
const isMissing = paramDef[ 'required' ] === true
2046+
2047+
return isMissing
2048+
} )
2049+
.filter( ( [ paramKey ] ) => {
2050+
const isProvided = userParams[ paramKey ] !== undefined
2051+
2052+
return !isProvided
2053+
} )
2054+
.map( ( [ paramKey ] ) => {
2055+
return paramKey
2056+
} )
2057+
2058+
if( missingParams.length > 0 ) {
2059+
const result = FlowMcpCli.#error( {
2060+
'error': `Missing required parameter(s): ${missingParams.join( ', ' )}`,
2061+
'fix': `Provide: ${appConfig[ 'cliCommand' ]} call ${toolName} '${JSON.stringify( expectedParameters, null, 0 )}'`
2062+
} )
2063+
2064+
return { result }
2065+
}
2066+
20302067
try {
20312068
const callResult = await matchedFunc( userParams )
20322069
const result = {
@@ -2263,10 +2300,9 @@ class FlowMcpCli {
22632300
return { result }
22642301
}
22652302

2266-
const localConfigPath = join( cwd, appConfig[ 'localConfigDirName' ], 'config.json' )
2267-
const { data: localConfig } = await FlowMcpCli.#readJson( { filePath: localConfigPath } )
2303+
const { toolRefs, source, groupName } = await FlowMcpCli.#resolveActiveToolRefs( { cwd } )
22682304

2269-
if( !localConfig || !localConfig[ 'tools' ] || localConfig[ 'tools' ].length === 0 ) {
2305+
if( toolRefs.length === 0 ) {
22702306
const result = FlowMcpCli.#error( {
22712307
'error': 'No active tools found.',
22722308
'fix': `Use ${appConfig[ 'cliCommand' ]} add <tool-name> to activate tools first.`
@@ -2293,15 +2329,8 @@ class FlowMcpCli {
22932329
}
22942330

22952331
const { toolRef } = matched
2296-
const previousLength = localConfig[ 'tools' ].length
2297-
localConfig[ 'tools' ] = localConfig[ 'tools' ]
2298-
.filter( ( ref ) => {
2299-
const shouldKeep = ref !== toolRef
2300-
2301-
return shouldKeep
2302-
} )
23032332

2304-
if( localConfig[ 'tools' ].length === previousLength ) {
2333+
if( !toolRefs.includes( toolRef ) ) {
23052334
const result = FlowMcpCli.#error( {
23062335
'error': `Tool "${toolName}" is not in active tools list.`,
23072336
'fix': `Use ${appConfig[ 'cliCommand' ]} list to see active tools.`
@@ -2310,6 +2339,27 @@ class FlowMcpCli {
23102339
return { result }
23112340
}
23122341

2342+
const localConfigPath = join( cwd, appConfig[ 'localConfigDirName' ], 'config.json' )
2343+
const { data: localConfig } = await FlowMcpCli.#readJson( { filePath: localConfigPath } )
2344+
2345+
if( source === 'group' ) {
2346+
const group = localConfig[ 'groups' ][ groupName ]
2347+
const toolsKey = Array.isArray( group[ 'tools' ] ) ? 'tools' : 'schemas'
2348+
group[ toolsKey ] = group[ toolsKey ]
2349+
.filter( ( ref ) => {
2350+
const shouldKeep = ref !== toolRef
2351+
2352+
return shouldKeep
2353+
} )
2354+
} else {
2355+
localConfig[ 'tools' ] = localConfig[ 'tools' ]
2356+
.filter( ( ref ) => {
2357+
const shouldKeep = ref !== toolRef
2358+
2359+
return shouldKeep
2360+
} )
2361+
}
2362+
23132363
await writeFile( localConfigPath, JSON.stringify( localConfig, null, 4 ), 'utf-8' )
23142364
await FlowMcpCli.#removeToolSchema( { toolName, cwd } )
23152365

@@ -2330,19 +2380,7 @@ class FlowMcpCli {
23302380
return { result }
23312381
}
23322382

2333-
const localConfigPath = join( cwd, appConfig[ 'localConfigDirName' ], 'config.json' )
2334-
const { data: localConfig } = await FlowMcpCli.#readJson( { filePath: localConfigPath } )
2335-
2336-
let toolRefs = []
2337-
if( localConfig && Array.isArray( localConfig[ 'tools' ] ) && localConfig[ 'tools' ].length > 0 ) {
2338-
toolRefs = localConfig[ 'tools' ]
2339-
} else if( localConfig && localConfig[ 'defaultGroup' ] ) {
2340-
const groupName = localConfig[ 'defaultGroup' ]
2341-
const group = localConfig[ 'groups' ] && localConfig[ 'groups' ][ groupName ]
2342-
if( group ) {
2343-
toolRefs = group[ 'tools' ] || group[ 'schemas' ] || []
2344-
}
2345-
}
2383+
const { toolRefs } = await FlowMcpCli.#resolveActiveToolRefs( { cwd } )
23462384

23472385
if( toolRefs.length === 0 ) {
23482386
const result = {
@@ -2372,6 +2410,7 @@ class FlowMcpCli {
23722410
}
23732411

23742412
const routes = schema[ 'routes' ]
2413+
const schemaTags = schema[ 'tags' ] || []
23752414
const requiredServerParams = schema[ 'requiredServerParams' ] || []
23762415
const { serverParams } = FlowMcpCli.#buildServerParams( { envObject, requiredServerParams } )
23772416

@@ -2384,8 +2423,11 @@ class FlowMcpCli {
23842423
routeName
23852424
} )
23862425

2387-
const schemaPath = join( appConfig[ 'localConfigDirName' ], 'tools', `${name}.json` )
2388-
tools.push( { name, description, 'schema': schemaPath } )
2426+
const routeConfig = routes[ routeName ]
2427+
const routeParameters = routeConfig[ 'parameters' ] || []
2428+
const { parameters } = FlowMcpCli.#extractParameters( { routeParameters } )
2429+
2430+
tools.push( { name, description, 'tags': schemaTags, parameters } )
23892431
} catch {
23902432
// skip broken tools
23912433
}
@@ -3941,19 +3983,44 @@ Note: Run "${cmd} init" first. This is the only interactive command.
39413983
}
39423984

39433985

3944-
static async #resolveAgentSchemas( { cwd } ) {
3986+
static async #resolveActiveToolRefs( { cwd } ) {
39453987
const localConfigPath = join( cwd, appConfig[ 'localConfigDirName' ], 'config.json' )
39463988
const { data: localConfig } = await FlowMcpCli.#readJson( { filePath: localConfigPath } )
39473989

3948-
if( !localConfig || !localConfig[ 'tools' ] || localConfig[ 'tools' ].length === 0 ) {
3990+
if( !localConfig ) {
3991+
return { 'toolRefs': [], 'source': null }
3992+
}
3993+
3994+
if( Array.isArray( localConfig[ 'tools' ] ) && localConfig[ 'tools' ].length > 0 ) {
3995+
return { 'toolRefs': localConfig[ 'tools' ], 'source': 'tools' }
3996+
}
3997+
3998+
if( localConfig[ 'defaultGroup' ] ) {
3999+
const groupName = localConfig[ 'defaultGroup' ]
4000+
const group = localConfig[ 'groups' ] && localConfig[ 'groups' ][ groupName ]
4001+
if( group ) {
4002+
const groupTools = group[ 'tools' ] || group[ 'schemas' ] || []
4003+
if( groupTools.length > 0 ) {
4004+
return { 'toolRefs': groupTools, 'source': 'group', 'groupName': groupName }
4005+
}
4006+
}
4007+
}
4008+
4009+
return { 'toolRefs': [], 'source': null }
4010+
}
4011+
4012+
4013+
static async #resolveAgentSchemas( { cwd } ) {
4014+
const { toolRefs } = await FlowMcpCli.#resolveActiveToolRefs( { cwd } )
4015+
4016+
if( toolRefs.length === 0 ) {
39494017
return {
39504018
'schemas': null,
39514019
'error': 'No active tools.',
39524020
'fix': `Use ${appConfig[ 'cliCommand' ]} add <tool-name> to activate tools.`
39534021
}
39544022
}
39554023

3956-
const toolRefs = localConfig[ 'tools' ]
39574024
const { schemas } = await FlowMcpCli.#resolveToolRefs( { toolRefs } )
39584025

39594026
return { schemas, 'error': null, 'fix': null }

0 commit comments

Comments
 (0)