The other answers described why this doesn't work. But IMO it's quite reasonable that you want this, and indeed Just 3 >>= \x -> Just 4 >>= \y -> foo x y is a bit of a silly solution to the task. Basically, the x and y values are independent of each other, yet you're fetching them sequentially, in a way that the complete y calculation could in principle depend on the value of x.
Monads aren't really the right abstraction here, they're too strong. To get x and y non-sequentially, you can use Applicative interface. The form that most Haskellers prefer nowadays (I think) is
foo <$> Just 3 <*> Just 4
You can read this as “zip the effectful values Just 3 and Just 4 together to a single action with two values, then apply foo over those values”.
...Actually that's not really how it works though, and for me that was super confusing when I first learned about applicatives. Namely, the above expression is in fact parsed as
(foo <$> Just 3) <*> Just 4
which looks again like it's sequential-style. But it's not, what going on here is only a currying/laziness trick to pass multiple values through the applicative value without having to group them to a suitable tuple. The code that literally works like I explained it would be
uncurry foo <$> ((,)<$>Just 3<*>Just 4)
Here, (,)<$>Just 3<*>Just 4 evaluates to Just (3,4). Then fmapping foo over that needs to be done in uncurried form, so the two arguments are accepted as a tuple. It's structurally clear, yet awkward because we're working against Haskell's curried style.
(Mathematically, this tupling is what's conceptually happing though: generally speaking, you're working in a monoidal category. Some other incarnations of applicative functors have such a tuppling-combinator as their underlying interface, instead of <*>; e.g. >*< from the invertible package.)
The trick with foo<$>Just 3<*>Just 4 is that instead of building a tuple, we start with partially applying foo to the 3 result. This doesn't actually require anything applicative/monadic yet – we're basically just transforming the contained value – in general: values – from 3 to foo 3, without touching their context. You may consider this a purely symbolic operation. Note that the type is Maybe (Int -> Int) at this point.
Then you use the <*> combinator to zip both of the Maybe contexts together, and simultaneously apply the foo 3 partially-evaluated function to its second argument.
I personally like this form, which is also equivalent:
liftA2 foo (Just 3) (Just 4)
We're not finished yet though: all the above suggestions give a result of type Maybe (Maybe Int). To flatten that into a Maybe Int, that's where you actually need the monad interface. One option is
join $ foo <$> Just 3 <*> Just 4